[FFmpeg-cvslog] Add support for Audible AAX (and AAX+) files
Vesselin Bontchev
git at videolan.org
Sun Jul 19 20:30:00 CEST 2015
ffmpeg | branch: master | Vesselin Bontchev <vesselin.bontchev at yandex.com> | Sat Jul 11 18:02:47 2015 +0000| [0a551cbe97e01e8b6426560bbdda2c28f22f0fb9] | committer: Michael Niedermayer
Add support for Audible AAX (and AAX+) files
Signed-off-by: Michael Niedermayer <michael at niedermayer.cc>
> http://git.videolan.org/gitweb.cgi/ffmpeg.git/?a=commit;h=0a551cbe97e01e8b6426560bbdda2c28f22f0fb9
---
doc/general.texi | 2 +
doc/muxers.texi | 7 +++
libavformat/isom.h | 8 ++++
libavformat/mov.c | 128 ++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 145 insertions(+)
diff --git a/doc/general.texi b/doc/general.texi
index dc22d90..617be66 100644
--- a/doc/general.texi
+++ b/doc/general.texi
@@ -226,6 +226,8 @@ library:
@item 4xm @tab @tab X
@tab 4X Technologies format, used in some games.
@item 8088flex TMV @tab @tab X
+ at 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
diff --git a/doc/muxers.texi b/doc/muxers.texi
index 8e3eb4b..ccd222c 100644
--- a/doc/muxers.texi
+++ b/doc/muxers.texi
@@ -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
+ at subsection Audible AAX
+
+Audible AAX files are encrypted M4B files, and they can be decrypted by specifying a 4 byte activation secret.
+ at example
+ffmpeg -activation_bytes 1CEB00DA -i test.aax -vn -c:a copy output.mp4
+ at end example
+
@section mp3
The MP3 muxer writes a raw MP3 stream with the following optional features:
diff --git a/libavformat/isom.h b/libavformat/isom.h
index 5d48989..aee9d6e 100644
--- a/libavformat/isom.h
+++ b/libavformat/isom.h
@@ -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);
diff --git a/libavformat/mov.c b/libavformat/mov.c
index 9d6b2e4..92bf2f8 100644
--- a/libavformat/mov.c
+++ b/libavformat/mov.c
@@ -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 },
};
More information about the ffmpeg-cvslog
mailing list