Commit 0a551cbe authored by Vesselin Bontchev's avatar Vesselin Bontchev Committed by Michael Niedermayer

Add support for Audible AAX (and AAX+) files

Signed-off-by: 's avatarMichael Niedermayer <michael@niedermayer.cc>
parent 4df66c7c
......@@ -226,6 +226,8 @@ library:
@item 4xm @tab @tab X
@tab 4X Technologies format, used in some games.
@item 8088flex TMV @tab @tab X
@item AAX @tab @tab X
@tab Audible Enhanced Audio format, used in audiobooks.
@item ACT Voice @tab @tab X
@tab contains G.729 audio
@item Adobe Filmstrip @tab X @tab X
......
......@@ -667,6 +667,13 @@ point on IIS with this muxer. Example:
ffmpeg -re @var{<normal input/transcoding options>} -movflags isml+frag_keyframe -f ismv http://server/publishingpoint.isml/Streams(Encoder1)
@end example
@subsection Audible AAX
Audible AAX files are encrypted M4B files, and they can be decrypted by specifying a 4 byte activation secret.
@example
ffmpeg -activation_bytes 1CEB00DA -i test.aax -vn -c:a copy output.mp4
@end example
@section mp3
The MP3 muxer writes a raw MP3 stream with the following optional features:
......
......@@ -198,6 +198,14 @@ typedef struct MOVContext {
MOVFragmentIndex** fragment_index_data;
unsigned fragment_index_count;
int atom_depth;
unsigned int aax_mode; ///< 'aax' file has been detected
uint8_t file_key[20];
uint8_t file_iv[20];
void *activation_bytes;
int activation_bytes_size;
void *audible_fixed_key;
int audible_fixed_key_size;
struct AVAES *aes_decrypt;
} MOVContext;
int ff_mp4_read_descr_len(AVIOContext *pb);
......
......@@ -37,6 +37,8 @@
#include "libavutil/dict.h"
#include "libavutil/display.h"
#include "libavutil/opt.h"
#include "libavutil/aes.h"
#include "libavutil/sha.h"
#include "libavutil/timecode.h"
#include "libavcodec/ac3tab.h"
#include "avformat.h"
......@@ -807,6 +809,120 @@ static int mov_read_mdat(MOVContext *c, AVIOContext *pb, MOVAtom atom)
return 0; /* now go for moov */
}
#define DRM_BLOB_SIZE 56
static int mov_read_adrm(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
uint8_t intermediate_key[20];
uint8_t intermediate_iv[20];
uint8_t input[64];
uint8_t output[64];
uint8_t file_checksum[20];
uint8_t calculated_checksum[20];
struct AVSHA *sha;
int i;
int ret = 0;
uint8_t *activation_bytes = c->activation_bytes;
uint8_t *fixed_key = c->audible_fixed_key;
c->aax_mode = 1;
sha = av_sha_alloc();
if (!sha)
return AVERROR(ENOMEM);
c->aes_decrypt = av_aes_alloc();
if (!c->aes_decrypt) {
ret = AVERROR(ENOMEM);
goto fail;
}
/* drm blob processing */
avio_read(pb, output, 8); // go to offset 8, absolute postion 0x251
avio_read(pb, input, DRM_BLOB_SIZE);
avio_read(pb, output, 4); // go to offset 4, absolute postion 0x28d
avio_read(pb, file_checksum, 20);
av_log(c->fc, AV_LOG_INFO, "[aax] file checksum == "); // required by external tools
for (i = 0; i < 20; i++)
av_log(sha, AV_LOG_INFO, "%02x", file_checksum[i]);
av_log(c->fc, AV_LOG_INFO, "\n");
/* verify activation data */
if (!activation_bytes || c->activation_bytes_size != 4) {
av_log(c->fc, AV_LOG_FATAL, "[aax] activation_bytes option is missing!\n");
ret = AVERROR(EINVAL);
goto fail;
}
if (c->activation_bytes_size != 4) {
av_log(c->fc, AV_LOG_FATAL, "[aax] activation_bytes value needs to be 4 bytes!\n");
ret = AVERROR(EINVAL);
goto fail;
}
/* verify fixed key */
if (c->audible_fixed_key_size != 16) {
av_log(c->fc, AV_LOG_FATAL, "[aax] audible_fixed_key value needs to be 16 bytes!\n");
ret = AVERROR(EINVAL);
goto fail;
}
/* AAX (and AAX+) key derivation */
av_sha_init(sha, 160);
av_sha_update(sha, fixed_key, 16);
av_sha_update(sha, activation_bytes, 4);
av_sha_final(sha, intermediate_key);
av_sha_init(sha, 160);
av_sha_update(sha, fixed_key, 16);
av_sha_update(sha, intermediate_key, 20);
av_sha_update(sha, activation_bytes, 4);
av_sha_final(sha, intermediate_iv);
av_sha_init(sha, 160);
av_sha_update(sha, intermediate_key, 16);
av_sha_update(sha, intermediate_iv, 16);
av_sha_final(sha, calculated_checksum);
if (memcmp(calculated_checksum, file_checksum, 20)) { // critical error
av_log(c->fc, AV_LOG_ERROR, "[aax] mismatch in checksums!\n");
ret = AVERROR_INVALIDDATA;
goto fail;
}
av_aes_init(c->aes_decrypt, intermediate_key, 128, 1);
av_aes_crypt(c->aes_decrypt, output, input, DRM_BLOB_SIZE >> 4, intermediate_iv, 1);
for (i = 0; i < 4; i++) {
// file data (in output) is stored in big-endian mode
if (activation_bytes[i] != output[3 - i]) { // critical error
av_log(c->fc, AV_LOG_ERROR, "[aax] error in drm blob decryption!\n");
ret = AVERROR_INVALIDDATA;
goto fail;
}
}
memcpy(c->file_key, output + 8, 16);
memcpy(input, output + 26, 16);
av_sha_init(sha, 160);
av_sha_update(sha, input, 16);
av_sha_update(sha, c->file_key, 16);
av_sha_update(sha, fixed_key, 16);
av_sha_final(sha, c->file_iv);
fail:
av_free(sha);
return ret;
}
// Audible AAX (and AAX+) bytestream decryption
static int aax_filter(uint8_t *input, int size, MOVContext *c)
{
int blocks = 0;
unsigned char iv[16];
memcpy(iv, c->file_iv, 16); // iv is overwritten
blocks = size >> 4; // trailing bytes are not encrypted!
av_aes_init(c->aes_decrypt, c->file_key, 128, 1);
av_aes_crypt(c->aes_decrypt, input, input, blocks, iv, 1);
return 0;
}
/* read major brand, minor version and compatible brands and store them as metadata */
static int mov_read_ftyp(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
......@@ -3637,6 +3753,7 @@ static const MOVParseTableEntry mov_default_parse_table[] = {
{ MKTAG('e','l','s','t'), mov_read_elst },
{ MKTAG('e','n','d','a'), mov_read_enda },
{ MKTAG('f','i','e','l'), mov_read_fiel },
{ MKTAG('a','d','r','m'), mov_read_adrm },
{ MKTAG('f','t','y','p'), mov_read_ftyp },
{ MKTAG('g','l','b','l'), mov_read_glbl },
{ MKTAG('h','d','l','r'), mov_read_hdlr },
......@@ -4058,6 +4175,8 @@ static int mov_read_close(AVFormatContext *s)
}
av_freep(&mov->fragment_index_data);
av_freep(&mov->aes_decrypt);
return 0;
}
......@@ -4477,6 +4596,9 @@ static int mov_read_packet(AVFormatContext *s, AVPacket *pkt)
pkt->flags |= sample->flags & AVINDEX_KEYFRAME ? AV_PKT_FLAG_KEY : 0;
pkt->pos = sample->pos;
if (mov->aax_mode)
aax_filter(pkt->data, pkt->size, mov);
return 0;
}
......@@ -4590,6 +4712,12 @@ static const AVOption mov_options[] = {
AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, .flags = FLAGS },
{ "export_xmp", "Export full XMP metadata", OFFSET(export_xmp),
AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, .flags = FLAGS },
{ "activation_bytes", "Secret bytes for Audible AAX files", OFFSET(activation_bytes),
AV_OPT_TYPE_BINARY, .flags = AV_OPT_FLAG_DECODING_PARAM },
{ "audible_fixed_key", // extracted from libAAX_SDK.so and AAXSDKWin.dll files!
"Fixed key used for handling Audible AAX files", OFFSET(audible_fixed_key),
AV_OPT_TYPE_BINARY, {.str="77214d4b196a87cd520045fd20a51d67"},
.flags = AV_OPT_FLAG_DECODING_PARAM },
{ NULL },
};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment