diff --git a/README.md b/README.md index f73aa24b43..99b20398f3 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ Shaka Packager supports: | AAC | I / O | - | I / O | I | | Dolby AC3/EAC3 | I / O | - | I | - | | DTS | I / O | - | - | - | + | FLAC | I / O | - | - | - | | Opus | *I / O* | I / O | - | - | | Vorbis | - | I / O | - | - | diff --git a/packager/app/test/packager_test.py b/packager/app/test/packager_test.py index 5ba6b40489..7c8aa8cc85 100755 --- a/packager/app/test/packager_test.py +++ b/packager/app/test/packager_test.py @@ -1071,6 +1071,17 @@ class PackagerFunctionalTest(PackagerAppTest): self._VerifyDecryption(self.output[0], 'bear-320x240-opus-golden.mp4') self._VerifyDecryption(self.output[1], 'bear-320x240-vp9-golden.mp4') + def testPackageFlacWithEncryption(self): + streams = [ + self._GetStream( + 'audio', output_format='mp4', test_file='bear-flac.mp4'), + ] + flags = self._GetFlags(encryption=True) + + self.assertPackageSuccess(streams, flags) + self._CheckTestResults('flac-with-encryption') + self._VerifyDecryption(self.output[0], 'bear-flac-golden.mp4') + def testPackageWvmInput(self): self.encryption_key = '9248d245390e0a49d483ba9b43fc69c3' self.assertPackageSuccess( diff --git a/packager/app/test/testdata/bear-flac-golden.mp4 b/packager/app/test/testdata/bear-flac-golden.mp4 new file mode 100644 index 0000000000..d9caf18d8a Binary files /dev/null and b/packager/app/test/testdata/bear-flac-golden.mp4 differ diff --git a/packager/app/test/testdata/flac-with-encryption/bear-flac-audio.mp4 b/packager/app/test/testdata/flac-with-encryption/bear-flac-audio.mp4 new file mode 100644 index 0000000000..0a51c057d8 Binary files /dev/null and b/packager/app/test/testdata/flac-with-encryption/bear-flac-audio.mp4 differ diff --git a/packager/app/test/testdata/flac-with-encryption/output.mpd b/packager/app/test/testdata/flac-with-encryption/output.mpd new file mode 100644 index 0000000000..f05517f962 --- /dev/null +++ b/packager/app/test/testdata/flac-with-encryption/output.mpd @@ -0,0 +1,19 @@ + + + + + + + + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== + + + + bear-flac-audio.mp4 + + + + + + + diff --git a/packager/media/base/audio_stream_info.cc b/packager/media/base/audio_stream_info.cc index e2135ef87a..c93eb14769 100644 --- a/packager/media/base/audio_stream_info.cc +++ b/packager/media/base/audio_stream_info.cc @@ -37,6 +37,8 @@ std::string AudioCodecToString(Codec codec) { return "DTS+"; case kCodecEAC3: return "EAC3"; + case kCodecFlac: + return "FLAC"; case kCodecOpus: return "Opus"; case kCodecVorbis: @@ -99,28 +101,30 @@ std::unique_ptr AudioStreamInfo::Clone() const { std::string AudioStreamInfo::GetCodecString(Codec codec, uint8_t audio_object_type) { switch (codec) { - case kCodecVorbis: - return "vorbis"; - case kCodecOpus: - return "opus"; case kCodecAAC: return "mp4a.40." + base::UintToString(audio_object_type); + case kCodecAC3: + return "ac-3"; case kCodecDTSC: return "dtsc"; + case kCodecDTSE: + return "dtse"; case kCodecDTSH: return "dtsh"; case kCodecDTSL: return "dtsl"; - case kCodecDTSE: - return "dtse"; - case kCodecDTSP: - return "dts+"; case kCodecDTSM: return "dts-"; - case kCodecAC3: - return "ac-3"; + case kCodecDTSP: + return "dts+"; case kCodecEAC3: return "ec-3"; + case kCodecFlac: + return "flac"; + case kCodecOpus: + return "opus"; + case kCodecVorbis: + return "vorbis"; default: NOTIMPLEMENTED() << "Codec: " << codec; return "unknown"; diff --git a/packager/media/base/fourccs.h b/packager/media/base/fourccs.h index 0448fdc16d..5390b0d417 100644 --- a/packager/media/base/fourccs.h +++ b/packager/media/base/fourccs.h @@ -42,6 +42,7 @@ enum FourCC : uint32_t { FOURCC_dash = 0x64617368, FOURCC_ddts = 0x64647473, FOURCC_dec3 = 0x64656333, + FOURCC_dfLa = 0x64664c61, FOURCC_dinf = 0x64696e66, FOURCC_dref = 0x64726566, FOURCC_dtsc = 0x64747363, @@ -57,6 +58,7 @@ enum FourCC : uint32_t { FOURCC_enca = 0x656e6361, FOURCC_encv = 0x656e6376, FOURCC_esds = 0x65736473, + FOURCC_fLaC = 0x664c6143, FOURCC_free = 0x66726565, FOURCC_frma = 0x66726d61, FOURCC_ftyp = 0x66747970, diff --git a/packager/media/base/stream_info.h b/packager/media/base/stream_info.h index 9019850036..20a6871e40 100644 --- a/packager/media/base/stream_info.h +++ b/packager/media/base/stream_info.h @@ -46,6 +46,7 @@ enum Codec { kCodecDTSM, kCodecDTSP, kCodecEAC3, + kCodecFlac, kCodecOpus, kCodecVorbis, kCodecAudioMaxPlusOne, diff --git a/packager/media/formats/mp4/box_definitions.cc b/packager/media/formats/mp4/box_definitions.cc index 3602031c1c..8d0fed6995 100644 --- a/packager/media/formats/mp4/box_definitions.cc +++ b/packager/media/formats/mp4/box_definitions.cc @@ -1774,6 +1774,27 @@ size_t OpusSpecific::ComputeSizeInternal() { kOpusMagicSignatureSize; } +FlacSpecific::FlacSpecific() {} +FlacSpecific::~FlacSpecific() {} + +FourCC FlacSpecific::BoxType() const { + return FOURCC_dfLa; +} + +bool FlacSpecific::ReadWriteInternal(BoxBuffer* buffer) { + RCHECK(ReadWriteHeaderInternal(buffer)); + size_t size = buffer->Reading() ? buffer->BytesLeft() : data.size(); + RCHECK(buffer->ReadWriteVector(&data, size)); + return true; +} + +size_t FlacSpecific::ComputeSizeInternal() { + // This box is optional. Skip it if not initialized. + if (data.empty()) + return 0; + return HeaderSize() + data.size(); +} + AudioSampleEntry::AudioSampleEntry() : format(FOURCC_NULL), data_reference_index(1), @@ -1818,6 +1839,7 @@ bool AudioSampleEntry::ReadWriteInternal(BoxBuffer* buffer) { RCHECK(buffer->TryReadWriteChild(&dac3)); RCHECK(buffer->TryReadWriteChild(&dec3)); RCHECK(buffer->TryReadWriteChild(&dops)); + RCHECK(buffer->TryReadWriteChild(&dfla)); // Somehow Edge does not support having sinf box before codec_configuration, // box, so just do it in the end of AudioSampleEntry. See @@ -1842,7 +1864,8 @@ size_t AudioSampleEntry::ComputeSizeInternal() { return HeaderSize() + sizeof(data_reference_index) + sizeof(channelcount) + sizeof(samplesize) + sizeof(samplerate) + sinf.ComputeSize() + esds.ComputeSize() + ddts.ComputeSize() + dac3.ComputeSize() + - dec3.ComputeSize() + dops.ComputeSize() + + dec3.ComputeSize() + dops.ComputeSize() + dfla.ComputeSize() + + // Reserved and predefined bytes. 6 + 8 + // 6 + 8 bytes reserved. 4; // 4 bytes predefined. } diff --git a/packager/media/formats/mp4/box_definitions.h b/packager/media/formats/mp4/box_definitions.h index 6b7c494dc0..2757a9685d 100644 --- a/packager/media/formats/mp4/box_definitions.h +++ b/packager/media/formats/mp4/box_definitions.h @@ -342,6 +342,15 @@ struct OpusSpecific : Box { uint16_t preskip; }; +// FLAC specific decoder configuration box: +// https://github.com/xiph/flac/blob/master/doc/isoflac.txt +// We do not care about the actual data inside, which is simply copied over. +struct FlacSpecific : FullBox { + DECLARE_BOX_METHODS(FlacSpecific); + + std::vector data; +}; + struct AudioSampleEntry : Box { DECLARE_BOX_METHODS(AudioSampleEntry); // Returns actual format of this sample entry. @@ -362,6 +371,7 @@ struct AudioSampleEntry : Box { AC3Specific dac3; EC3Specific dec3; OpusSpecific dops; + FlacSpecific dfla; }; struct WebVTTConfigurationBox : Box { diff --git a/packager/media/formats/mp4/box_definitions_unittest.cc b/packager/media/formats/mp4/box_definitions_unittest.cc index 1f1fdcd9d7..ea2e0f1bd8 100644 --- a/packager/media/formats/mp4/box_definitions_unittest.cc +++ b/packager/media/formats/mp4/box_definitions_unittest.cc @@ -434,6 +434,16 @@ class BoxDefinitionsTestGeneral : public testing::Test { dops->opus_identification_header.size() - 1); } + void Fill(FlacSpecific* dfla) { + const uint8_t kFlacData[] = {0x50, 0x11, 0x60}; + dfla->data.assign(std::begin(kFlacData), std::end(kFlacData)); + } + + void Modify(FlacSpecific* dfla) { + const uint8_t kFlacData[] = {0x50, 0x11, 0x40}; + dfla->data.assign(std::begin(kFlacData), std::end(kFlacData)); + } + void Fill(AudioSampleEntry* enca) { enca->format = FOURCC_enca; enca->data_reference_index = 2; @@ -1234,6 +1244,21 @@ TEST_F(BoxDefinitionsTest, OpusSampleEntry) { ASSERT_EQ(entry, entry_readback); } +TEST_F(BoxDefinitionsTest, FlacSampleEntry) { + AudioSampleEntry entry; + entry.format = FOURCC_fLaC; + entry.data_reference_index = 2; + entry.channelcount = 5; + entry.samplesize = 16; + entry.samplerate = 44100; + Fill(&entry.dfla); + entry.Write(this->buffer_.get()); + + AudioSampleEntry entry_readback; + ASSERT_TRUE(ReadBack(&entry_readback)); + ASSERT_EQ(entry, entry_readback); +} + TEST_F(BoxDefinitionsTest, CompactSampleSize_FieldSize16) { CompactSampleSize stz2; stz2.field_size = 16; diff --git a/packager/media/formats/mp4/mp4_media_parser.cc b/packager/media/formats/mp4/mp4_media_parser.cc index cf0f238ee2..b7d5bd12c7 100644 --- a/packager/media/formats/mp4/mp4_media_parser.cc +++ b/packager/media/formats/mp4/mp4_media_parser.cc @@ -89,6 +89,8 @@ Codec FourCCToCodec(FourCC fourcc) { return kCodecAC3; case FOURCC_ec_3: return kCodecEAC3; + case FOURCC_fLaC: + return kCodecFlac; default: return kUnknownCodec; } @@ -425,12 +427,12 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { break; case FOURCC_dtsc: FALLTHROUGH_INTENDED; + case FOURCC_dtse: + FALLTHROUGH_INTENDED; case FOURCC_dtsh: FALLTHROUGH_INTENDED; case FOURCC_dtsl: FALLTHROUGH_INTENDED; - case FOURCC_dtse: - FALLTHROUGH_INTENDED; case FOURCC_dtsm: codec_config = entry.ddts.extra_data; max_bitrate = entry.ddts.max_bitrate; @@ -448,6 +450,11 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { num_channels = static_cast(GetEc3NumChannels(codec_config)); sampling_frequency = entry.samplerate; break; + case FOURCC_fLaC: + codec_config = entry.dfla.data; + num_channels = entry.channelcount; + sampling_frequency = entry.samplerate; + break; case FOURCC_Opus: codec_config = entry.dops.opus_identification_header; num_channels = entry.channelcount; diff --git a/packager/media/formats/mp4/mp4_muxer.cc b/packager/media/formats/mp4/mp4_muxer.cc index 155a19d158..6d12e3e083 100644 --- a/packager/media/formats/mp4/mp4_muxer.cc +++ b/packager/media/formats/mp4/mp4_muxer.cc @@ -73,6 +73,8 @@ FourCC CodecToFourCC(Codec codec, H26xStreamFormat h26x_stream_format) { return FOURCC_dtsm; case kCodecEAC3: return FOURCC_ec_3; + case kCodecFlac: + return FOURCC_fLaC; case kCodecOpus: return FOURCC_Opus; default: @@ -367,6 +369,9 @@ void MP4Muxer::GenerateAudioTrak(const AudioStreamInfo* audio_info, case kCodecEAC3: audio.dec3.data = audio_info->codec_config(); break; + case kCodecFlac: + audio.dfla.data = audio_info->codec_config(); + break; case kCodecOpus: audio.dops.opus_identification_header = audio_info->codec_config(); break; diff --git a/packager/media/test/data/README b/packager/media/test/data/README index 82f91a76e1..0311df8c15 100644 --- a/packager/media/test/data/README +++ b/packager/media/test/data/README @@ -41,6 +41,10 @@ bear-640x360-trailing-moov-additional-mdat.mp4 - Same content, but with moov bo bear-640x360-av_frag.mp4 - Same content, but in fragmented mp4. bear-640x360-aac_lc-silent_right.mp4 - Audio only, stereo, but right channel is silent, with AAC-LC profile. bear-640x360-aac_he-silent_right.mp4 - Same as above, but with AAC-HE profile. +bear-flac.mp4 - Unfragmented audio-only 44.1kHz FLAC in MP4 file, created using: + ffmpeg -i bear-1280x720.mp4 -map 0:0 -acodec flac -strict -2 bear-flac.mp4 + Note, "-strict -2" was required because current ffmpeg libavformat version + 57.75.100 indicates that flac in MP4 support is experimental. // Non square pixels. bear-640x360-non_square_pixel-with_pasp.mp4 - A non-square pixel version of the video track of bear-640x360.mp4 with PixelAspectRatio box. diff --git a/packager/media/test/data/bear-flac.mp4 b/packager/media/test/data/bear-flac.mp4 new file mode 100644 index 0000000000..ac3686b582 Binary files /dev/null and b/packager/media/test/data/bear-flac.mp4 differ