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