Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
Vesselin Bontchev authored on 2015/07/12 03:02:47... | ... |
@@ -226,6 +226,8 @@ library: |
226 | 226 |
@item 4xm @tab @tab X |
227 | 227 |
@tab 4X Technologies format, used in some games. |
228 | 228 |
@item 8088flex TMV @tab @tab X |
229 |
+@item AAX @tab @tab X |
|
230 |
+ @tab Audible Enhanced Audio format, used in audiobooks. |
|
229 | 231 |
@item ACT Voice @tab @tab X |
230 | 232 |
@tab contains G.729 audio |
231 | 233 |
@item Adobe Filmstrip @tab X @tab X |
... | ... |
@@ -667,6 +667,13 @@ point on IIS with this muxer. Example: |
667 | 667 |
ffmpeg -re @var{<normal input/transcoding options>} -movflags isml+frag_keyframe -f ismv http://server/publishingpoint.isml/Streams(Encoder1) |
668 | 668 |
@end example |
669 | 669 |
|
670 |
+@subsection Audible AAX |
|
671 |
+ |
|
672 |
+Audible AAX files are encrypted M4B files, and they can be decrypted by specifying a 4 byte activation secret. |
|
673 |
+@example |
|
674 |
+ffmpeg -activation_bytes 1CEB00DA -i test.aax -vn -c:a copy output.mp4 |
|
675 |
+@end example |
|
676 |
+ |
|
670 | 677 |
@section mp3 |
671 | 678 |
|
672 | 679 |
The MP3 muxer writes a raw MP3 stream with the following optional features: |
... | ... |
@@ -198,6 +198,14 @@ typedef struct MOVContext { |
198 | 198 |
MOVFragmentIndex** fragment_index_data; |
199 | 199 |
unsigned fragment_index_count; |
200 | 200 |
int atom_depth; |
201 |
+ unsigned int aax_mode; ///< 'aax' file has been detected |
|
202 |
+ uint8_t file_key[20]; |
|
203 |
+ uint8_t file_iv[20]; |
|
204 |
+ void *activation_bytes; |
|
205 |
+ int activation_bytes_size; |
|
206 |
+ void *audible_fixed_key; |
|
207 |
+ int audible_fixed_key_size; |
|
208 |
+ struct AVAES *aes_decrypt; |
|
201 | 209 |
} MOVContext; |
202 | 210 |
|
203 | 211 |
int ff_mp4_read_descr_len(AVIOContext *pb); |
... | ... |
@@ -37,6 +37,8 @@ |
37 | 37 |
#include "libavutil/dict.h" |
38 | 38 |
#include "libavutil/display.h" |
39 | 39 |
#include "libavutil/opt.h" |
40 |
+#include "libavutil/aes.h" |
|
41 |
+#include "libavutil/sha.h" |
|
40 | 42 |
#include "libavutil/timecode.h" |
41 | 43 |
#include "libavcodec/ac3tab.h" |
42 | 44 |
#include "avformat.h" |
... | ... |
@@ -807,6 +809,120 @@ static int mov_read_mdat(MOVContext *c, AVIOContext *pb, MOVAtom atom) |
807 | 807 |
return 0; /* now go for moov */ |
808 | 808 |
} |
809 | 809 |
|
810 |
+#define DRM_BLOB_SIZE 56 |
|
811 |
+ |
|
812 |
+static int mov_read_adrm(MOVContext *c, AVIOContext *pb, MOVAtom atom) |
|
813 |
+{ |
|
814 |
+ uint8_t intermediate_key[20]; |
|
815 |
+ uint8_t intermediate_iv[20]; |
|
816 |
+ uint8_t input[64]; |
|
817 |
+ uint8_t output[64]; |
|
818 |
+ uint8_t file_checksum[20]; |
|
819 |
+ uint8_t calculated_checksum[20]; |
|
820 |
+ struct AVSHA *sha; |
|
821 |
+ int i; |
|
822 |
+ int ret = 0; |
|
823 |
+ uint8_t *activation_bytes = c->activation_bytes; |
|
824 |
+ uint8_t *fixed_key = c->audible_fixed_key; |
|
825 |
+ |
|
826 |
+ c->aax_mode = 1; |
|
827 |
+ |
|
828 |
+ sha = av_sha_alloc(); |
|
829 |
+ if (!sha) |
|
830 |
+ return AVERROR(ENOMEM); |
|
831 |
+ c->aes_decrypt = av_aes_alloc(); |
|
832 |
+ if (!c->aes_decrypt) { |
|
833 |
+ ret = AVERROR(ENOMEM); |
|
834 |
+ goto fail; |
|
835 |
+ } |
|
836 |
+ |
|
837 |
+ /* drm blob processing */ |
|
838 |
+ avio_read(pb, output, 8); // go to offset 8, absolute postion 0x251 |
|
839 |
+ avio_read(pb, input, DRM_BLOB_SIZE); |
|
840 |
+ avio_read(pb, output, 4); // go to offset 4, absolute postion 0x28d |
|
841 |
+ avio_read(pb, file_checksum, 20); |
|
842 |
+ |
|
843 |
+ av_log(c->fc, AV_LOG_INFO, "[aax] file checksum == "); // required by external tools |
|
844 |
+ for (i = 0; i < 20; i++) |
|
845 |
+ av_log(sha, AV_LOG_INFO, "%02x", file_checksum[i]); |
|
846 |
+ av_log(c->fc, AV_LOG_INFO, "\n"); |
|
847 |
+ |
|
848 |
+ /* verify activation data */ |
|
849 |
+ if (!activation_bytes || c->activation_bytes_size != 4) { |
|
850 |
+ av_log(c->fc, AV_LOG_FATAL, "[aax] activation_bytes option is missing!\n"); |
|
851 |
+ ret = AVERROR(EINVAL); |
|
852 |
+ goto fail; |
|
853 |
+ } |
|
854 |
+ if (c->activation_bytes_size != 4) { |
|
855 |
+ av_log(c->fc, AV_LOG_FATAL, "[aax] activation_bytes value needs to be 4 bytes!\n"); |
|
856 |
+ ret = AVERROR(EINVAL); |
|
857 |
+ goto fail; |
|
858 |
+ } |
|
859 |
+ |
|
860 |
+ /* verify fixed key */ |
|
861 |
+ if (c->audible_fixed_key_size != 16) { |
|
862 |
+ av_log(c->fc, AV_LOG_FATAL, "[aax] audible_fixed_key value needs to be 16 bytes!\n"); |
|
863 |
+ ret = AVERROR(EINVAL); |
|
864 |
+ goto fail; |
|
865 |
+ } |
|
866 |
+ |
|
867 |
+ /* AAX (and AAX+) key derivation */ |
|
868 |
+ av_sha_init(sha, 160); |
|
869 |
+ av_sha_update(sha, fixed_key, 16); |
|
870 |
+ av_sha_update(sha, activation_bytes, 4); |
|
871 |
+ av_sha_final(sha, intermediate_key); |
|
872 |
+ av_sha_init(sha, 160); |
|
873 |
+ av_sha_update(sha, fixed_key, 16); |
|
874 |
+ av_sha_update(sha, intermediate_key, 20); |
|
875 |
+ av_sha_update(sha, activation_bytes, 4); |
|
876 |
+ av_sha_final(sha, intermediate_iv); |
|
877 |
+ av_sha_init(sha, 160); |
|
878 |
+ av_sha_update(sha, intermediate_key, 16); |
|
879 |
+ av_sha_update(sha, intermediate_iv, 16); |
|
880 |
+ av_sha_final(sha, calculated_checksum); |
|
881 |
+ if (memcmp(calculated_checksum, file_checksum, 20)) { // critical error |
|
882 |
+ av_log(c->fc, AV_LOG_ERROR, "[aax] mismatch in checksums!\n"); |
|
883 |
+ ret = AVERROR_INVALIDDATA; |
|
884 |
+ goto fail; |
|
885 |
+ } |
|
886 |
+ av_aes_init(c->aes_decrypt, intermediate_key, 128, 1); |
|
887 |
+ av_aes_crypt(c->aes_decrypt, output, input, DRM_BLOB_SIZE >> 4, intermediate_iv, 1); |
|
888 |
+ for (i = 0; i < 4; i++) { |
|
889 |
+ // file data (in output) is stored in big-endian mode |
|
890 |
+ if (activation_bytes[i] != output[3 - i]) { // critical error |
|
891 |
+ av_log(c->fc, AV_LOG_ERROR, "[aax] error in drm blob decryption!\n"); |
|
892 |
+ ret = AVERROR_INVALIDDATA; |
|
893 |
+ goto fail; |
|
894 |
+ } |
|
895 |
+ } |
|
896 |
+ memcpy(c->file_key, output + 8, 16); |
|
897 |
+ memcpy(input, output + 26, 16); |
|
898 |
+ av_sha_init(sha, 160); |
|
899 |
+ av_sha_update(sha, input, 16); |
|
900 |
+ av_sha_update(sha, c->file_key, 16); |
|
901 |
+ av_sha_update(sha, fixed_key, 16); |
|
902 |
+ av_sha_final(sha, c->file_iv); |
|
903 |
+ |
|
904 |
+fail: |
|
905 |
+ av_free(sha); |
|
906 |
+ |
|
907 |
+ return ret; |
|
908 |
+} |
|
909 |
+ |
|
910 |
+// Audible AAX (and AAX+) bytestream decryption |
|
911 |
+static int aax_filter(uint8_t *input, int size, MOVContext *c) |
|
912 |
+{ |
|
913 |
+ int blocks = 0; |
|
914 |
+ unsigned char iv[16]; |
|
915 |
+ |
|
916 |
+ memcpy(iv, c->file_iv, 16); // iv is overwritten |
|
917 |
+ blocks = size >> 4; // trailing bytes are not encrypted! |
|
918 |
+ av_aes_init(c->aes_decrypt, c->file_key, 128, 1); |
|
919 |
+ av_aes_crypt(c->aes_decrypt, input, input, blocks, iv, 1); |
|
920 |
+ |
|
921 |
+ return 0; |
|
922 |
+} |
|
923 |
+ |
|
810 | 924 |
/* read major brand, minor version and compatible brands and store them as metadata */ |
811 | 925 |
static int mov_read_ftyp(MOVContext *c, AVIOContext *pb, MOVAtom atom) |
812 | 926 |
{ |
... | ... |
@@ -3637,6 +3753,7 @@ static const MOVParseTableEntry mov_default_parse_table[] = { |
3637 | 3637 |
{ MKTAG('e','l','s','t'), mov_read_elst }, |
3638 | 3638 |
{ MKTAG('e','n','d','a'), mov_read_enda }, |
3639 | 3639 |
{ MKTAG('f','i','e','l'), mov_read_fiel }, |
3640 |
+{ MKTAG('a','d','r','m'), mov_read_adrm }, |
|
3640 | 3641 |
{ MKTAG('f','t','y','p'), mov_read_ftyp }, |
3641 | 3642 |
{ MKTAG('g','l','b','l'), mov_read_glbl }, |
3642 | 3643 |
{ MKTAG('h','d','l','r'), mov_read_hdlr }, |
... | ... |
@@ -4058,6 +4175,8 @@ static int mov_read_close(AVFormatContext *s) |
4058 | 4058 |
} |
4059 | 4059 |
av_freep(&mov->fragment_index_data); |
4060 | 4060 |
|
4061 |
+ av_freep(&mov->aes_decrypt); |
|
4062 |
+ |
|
4061 | 4063 |
return 0; |
4062 | 4064 |
} |
4063 | 4065 |
|
... | ... |
@@ -4477,6 +4596,9 @@ static int mov_read_packet(AVFormatContext *s, AVPacket *pkt) |
4477 | 4477 |
pkt->flags |= sample->flags & AVINDEX_KEYFRAME ? AV_PKT_FLAG_KEY : 0; |
4478 | 4478 |
pkt->pos = sample->pos; |
4479 | 4479 |
|
4480 |
+ if (mov->aax_mode) |
|
4481 |
+ aax_filter(pkt->data, pkt->size, mov); |
|
4482 |
+ |
|
4480 | 4483 |
return 0; |
4481 | 4484 |
} |
4482 | 4485 |
|
... | ... |
@@ -4590,6 +4712,12 @@ static const AVOption mov_options[] = { |
4590 | 4590 |
AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, .flags = FLAGS }, |
4591 | 4591 |
{ "export_xmp", "Export full XMP metadata", OFFSET(export_xmp), |
4592 | 4592 |
AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, .flags = FLAGS }, |
4593 |
+ { "activation_bytes", "Secret bytes for Audible AAX files", OFFSET(activation_bytes), |
|
4594 |
+ AV_OPT_TYPE_BINARY, .flags = AV_OPT_FLAG_DECODING_PARAM }, |
|
4595 |
+ { "audible_fixed_key", // extracted from libAAX_SDK.so and AAXSDKWin.dll files! |
|
4596 |
+ "Fixed key used for handling Audible AAX files", OFFSET(audible_fixed_key), |
|
4597 |
+ AV_OPT_TYPE_BINARY, {.str="77214d4b196a87cd520045fd20a51d67"}, |
|
4598 |
+ .flags = AV_OPT_FLAG_DECODING_PARAM }, |
|
4593 | 4599 |
{ NULL }, |
4594 | 4600 |
}; |
4595 | 4601 |
|