mirror of
https://git.ffmpeg.org/ffmpeg.git
synced 2025-01-18 21:30:55 +00:00
Add support for Audible AAX (and AAX+) files
Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
This commit is contained in:
parent
4df66c7cd6
commit
0a551cbe97
@ -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 },
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user