diff --git a/AUTHORS b/AUTHORS index 8c009d283d..4c46524d6e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -17,6 +17,7 @@ Alen Vrecko Anders Hasselqvist Chun-da Chen Daniel CantarĂ­n +Evgeny Zajcev Google Inc. <*@google.com> Ivi.ru LLC <*@ivi.ru> Leandro Moreira diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 773f16c1d9..393125947d 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -28,6 +28,7 @@ Bei Li Chun-da Chen Daniel CantarĂ­n David Cavar +Evgeny Zajcev Gabe Kopley Haoming Chen Jacob Trimble diff --git a/README.md b/README.md index 792aa1a64c..76892dceae 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ Shaka Packager supports: | VP9 | I / O | I / O | - | - | - | | AV1 | I / O | I / O | - | - | - | | AAC | I / O | - | I / O | I | O | + | MP3 | - | - | I / O | - | O | | Dolby AC3 | I / O | - | I / O | - | O | | Dolby EAC3 | I / O | - | O | - | O | | DTS | I / O | - | - | - | - | diff --git a/packager/app/muxer_factory.cc b/packager/app/muxer_factory.cc index d4f93707be..159889c5ca 100644 --- a/packager/app/muxer_factory.cc +++ b/packager/app/muxer_factory.cc @@ -40,6 +40,7 @@ std::shared_ptr MuxerFactory::CreateMuxer( switch (output_format) { case CONTAINER_AAC: + case CONTAINER_MP3: case CONTAINER_AC3: case CONTAINER_EAC3: muxer = std::make_shared(options); diff --git a/packager/media/base/audio_stream_info.cc b/packager/media/base/audio_stream_info.cc index c93eb14769..776bf92d2b 100644 --- a/packager/media/base/audio_stream_info.cc +++ b/packager/media/base/audio_stream_info.cc @@ -43,6 +43,8 @@ std::string AudioCodecToString(Codec codec) { return "Opus"; case kCodecVorbis: return "Vorbis"; + case kCodecMP3: + return "MP3"; default: NOTIMPLEMENTED() << "Unknown Audio Codec: " << codec; return "UnknownCodec"; @@ -123,6 +125,8 @@ std::string AudioStreamInfo::GetCodecString(Codec codec, return "flac"; case kCodecOpus: return "opus"; + case kCodecMP3: + return "mp3"; case kCodecVorbis: return "vorbis"; default: diff --git a/packager/media/base/container_names.cc b/packager/media/base/container_names.cc index 78d028c8ac..d18f35cfd3 100644 --- a/packager/media/base/container_names.cc +++ b/packager/media/base/container_names.cc @@ -1734,6 +1734,8 @@ MediaContainerName DetermineContainerFromFormatName( } else if (base::EqualsCaseInsensitiveASCII(format_name, "ec3") || base::EqualsCaseInsensitiveASCII(format_name, "eac3")) { return CONTAINER_EAC3; + } else if (base::EqualsCaseInsensitiveASCII(format_name, "mp3")) { + return CONTAINER_MP3; } else if (base::EqualsCaseInsensitiveASCII(format_name, "webm")) { return CONTAINER_WEBM; } else if (base::EqualsCaseInsensitiveASCII(format_name, "cmfa") || diff --git a/packager/media/base/fourccs.h b/packager/media/base/fourccs.h index d62435b9e8..1dba254afb 100644 --- a/packager/media/base/fourccs.h +++ b/packager/media/base/fourccs.h @@ -90,6 +90,7 @@ enum FourCC : uint32_t { FOURCC_minf = 0x6d696e66, FOURCC_moof = 0x6d6f6f66, FOURCC_moov = 0x6d6f6f76, + FOURCC_mp3a = 0x6d703361, FOURCC_mp41 = 0x6d703431, FOURCC_mp4a = 0x6d703461, FOURCC_mp4v = 0x6d703476, diff --git a/packager/media/base/stream_info.h b/packager/media/base/stream_info.h index ba5bbc7dcd..47872a9f34 100644 --- a/packager/media/base/stream_info.h +++ b/packager/media/base/stream_info.h @@ -52,6 +52,7 @@ enum Codec { kCodecFlac, kCodecOpus, kCodecVorbis, + kCodecMP3, kCodecAudioMaxPlusOne, kCodecText = 300, diff --git a/packager/media/formats/mp2t/es_parser_audio.cc b/packager/media/formats/mp2t/es_parser_audio.cc index 2a8d96c3e4..3a0d99683e 100644 --- a/packager/media/formats/mp2t/es_parser_audio.cc +++ b/packager/media/formats/mp2t/es_parser_audio.cc @@ -18,6 +18,7 @@ #include "packager/media/formats/mp2t/ac3_header.h" #include "packager/media/formats/mp2t/adts_header.h" #include "packager/media/formats/mp2t/mp2t_common.h" +#include "packager/media/formats/mp2t/mpeg1_header.h" #include "packager/media/formats/mp2t/ts_stream_type.h" namespace shaka { @@ -98,6 +99,8 @@ EsParserAudio::EsParserAudio(uint32_t pid, sbr_in_mimetype_(sbr_in_mimetype) { if (stream_type == TsStreamType::kAc3) { audio_header_.reset(new Ac3Header); + } else if (stream_type == TsStreamType::kMpeg1Audio) { + audio_header_.reset(new Mpeg1Header); } else { DCHECK_EQ(stream_type, TsStreamType::kAdtsAac); audio_header_.reset(new AdtsHeader); @@ -214,7 +217,9 @@ bool EsParserAudio::UpdateAudioConfiguration(const AudioHeader& audio_header) { : samples_per_second; const Codec codec = - stream_type_ == TsStreamType::kAc3 ? kCodecAC3 : kCodecAAC; + stream_type_ == TsStreamType::kAc3 + ? kCodecAC3 + : (stream_type_ == TsStreamType::kMpeg1Audio ? kCodecMP3 : kCodecAAC); last_audio_decoder_config_ = std::make_shared( pid(), kMpeg2Timescale, kInfiniteDuration, codec, AudioStreamInfo::GetCodecString(codec, audio_header.GetObjectType()), diff --git a/packager/media/formats/mp2t/mp2t.gyp b/packager/media/formats/mp2t/mp2t.gyp index 64e5562934..267b5d8de6 100644 --- a/packager/media/formats/mp2t/mp2t.gyp +++ b/packager/media/formats/mp2t/mp2t.gyp @@ -31,6 +31,8 @@ 'es_parser.h', 'mp2t_media_parser.cc', 'mp2t_media_parser.h', + 'mpeg1_header.cc', + 'mpeg1_header.h', 'pes_packet.cc', 'pes_packet.h', 'pes_packet_generator.cc', @@ -72,6 +74,7 @@ 'es_parser_h264_unittest.cc', 'es_parser_h26x_unittest.cc', 'mp2t_media_parser_unittest.cc', + 'mpeg1_header_unittest.cc', 'pes_packet_generator_unittest.cc', 'program_map_table_writer_unittest.cc', 'ts_segmenter_unittest.cc', diff --git a/packager/media/formats/mp2t/mp2t_media_parser.cc b/packager/media/formats/mp2t/mp2t_media_parser.cc index 4ecb9dd76a..0e003defa7 100644 --- a/packager/media/formats/mp2t/mp2t_media_parser.cc +++ b/packager/media/formats/mp2t/mp2t_media_parser.cc @@ -298,6 +298,7 @@ void Mp2tMediaParser::RegisterPes(int pmt_pid, base::Bind(&Mp2tMediaParser::OnEmitSample, base::Unretained(this)))); break; case TsStreamType::kAdtsAac: + case TsStreamType::kMpeg1Audio: case TsStreamType::kAc3: es_parser.reset(new EsParserAudio( pes_pid, static_cast(stream_type), diff --git a/packager/media/formats/mp2t/mpeg1_header.cc b/packager/media/formats/mp2t/mpeg1_header.cc new file mode 100644 index 0000000000..7ffb89b458 --- /dev/null +++ b/packager/media/formats/mp2t/mpeg1_header.cc @@ -0,0 +1,234 @@ +#include "packager/media/formats/mp2t/mpeg1_header.h" + +#include "packager/media/base/bit_reader.h" +#include "packager/media/base/bit_writer.h" +#include "packager/media/formats/mp2t/mp2t_common.h" + +// Parsing is done according to +// https://www.datavoyage.com/mpgscript/mpeghdr.htm +namespace { +const size_t kMpeg1HeaderMinSize = 4; + +const uint8_t kMpeg1V_INV = 0b01; /* Invalid version */ +const uint8_t kMpeg1L_INV = 0b00; /* Invalid layer */ + +const uint8_t kMpeg1L_3 = 0b01; +const uint8_t kMpeg1L_2 = 0b10; +const uint8_t kMpeg1L_1 = 0b11; + +const size_t kMpeg1SamplesPerFrameTable[] = { + // L1, L2, L3 + 384, 1152, 1152}; + +const uint32_t kMpeg1SampleRateTable[][3] = { + // clang-format off + // V1, V2, V2.5 + {44100, 22050, 11025}, + {48000, 24000, 12000}, + {32000, 16000, 8000}}; + // clang-format on +const size_t kMpeg1SampleRateTableSize = arraysize(kMpeg1SampleRateTable); + +static inline uint32_t Mpeg1SampleRate(uint8_t sr_idx, uint8_t version) { + static int sr_version_indexes[] = {2, -1, 1, 0}; // {V2.5, RESERVED, V2, V1} + DCHECK_NE(version, 1); + DCHECK_LT(sr_idx, kMpeg1SampleRateTableSize); + return kMpeg1SampleRateTable[sr_idx][sr_version_indexes[version]]; +} + +const uint32_t kMpeg1BitrateTable[][5] = { + // clang-format off + // V1:L1, V1:L2, V1:L3, V2:L1, V2&V2.5:L2&L3 + { 0, 0, 0, 0, 0}, + { 32, 32, 32, 32, 8}, + { 64, 48, 40, 48, 16}, + { 96, 56, 48, 56, 24}, + { 128, 64, 56, 64, 32}, + { 160, 80, 64, 80, 40}, + { 192, 96, 80, 96, 48}, + { 224, 112, 96, 112, 56}, + { 256, 128, 112, 128, 64}, + { 288, 160, 128, 144, 80}, + { 320, 192, 160, 160, 96}, + { 352, 224, 192, 176, 112}, + { 384, 256, 224, 192, 128}, + { 416, 320, 256, 224, 144}, + { 448, 384, 320, 256, 160}}; + // clang-format on +const size_t kMpeg1BitrateTableSize = arraysize(kMpeg1BitrateTable); + +static inline uint32_t Mpeg1BitRate(uint8_t btr_idx, + uint8_t version, + uint8_t layer) { + static int btr_version_indexes[] = {1, -1, 1, 0}; // {V2.5, RESERVED, V2, V1} + static int btr_layer_indexes[] = {-1, 2, 1, 0}; // {RESERVED, L3, L2, L1} + + DCHECK_NE(version, 1); + DCHECK_NE(layer, 0); + int vidx = btr_version_indexes[version]; + int lidx = btr_layer_indexes[layer]; + if (vidx == 1 && lidx > 1) + lidx = 1; + + DCHECK_LT(vidx * 3 + lidx, 5); + DCHECK_LT(btr_idx, kMpeg1BitrateTableSize); + return kMpeg1BitrateTable[btr_idx][vidx * 3 + lidx] * 1000; +} + +static inline size_t Mpeg1FrameSize(uint8_t layer, + uint32_t bitrate, + uint32_t sample_rate, + uint8_t padded) { + DCHECK_GT(sample_rate, static_cast(0)); + if (layer == kMpeg1L_1) + return (12 * bitrate / sample_rate + padded) * 4; + return 144 * bitrate / sample_rate + padded; +} + +} // namespace + +namespace shaka { +namespace media { +namespace mp2t { + +bool Mpeg1Header::IsSyncWord(const uint8_t* buf) const { + return (buf[0] == 0xff) && + ((buf[1] & 0b11100000) == 0b11100000) + // Version 01 is reserved + && ((buf[1] & 0b00011000) != 0b00001000) + // Layer 00 is reserved + && ((buf[1] & 0b00000110) != 0b00000000); +} + +size_t Mpeg1Header::GetMinFrameSize() const { + return kMpeg1HeaderMinSize + 1; +} + +size_t Mpeg1Header::GetSamplesPerFrame() const { + static int spf_layer_indexes[] = {-1, 2, 1, 0}; // {RESERVED, L3, L2, L1} + DCHECK_NE(layer_, 0); + return kMpeg1SamplesPerFrameTable[spf_layer_indexes[layer_]]; +} + +bool Mpeg1Header::Parse(const uint8_t* mpeg1_frame, size_t mpeg1_frame_size) { + DCHECK(mpeg1_frame); + + if (mpeg1_frame_size < kMpeg1HeaderMinSize) + return false; + + BitReader frame(mpeg1_frame, mpeg1_frame_size); + // Verify frame starts with sync bits (0x7ff). + uint32_t sync; + RCHECK(frame.ReadBits(11, &sync)); + RCHECK(sync == 0x7ff); + // MPEG version and layer. + RCHECK(frame.ReadBits(2, &version_)); + RCHECK(version_ != kMpeg1V_INV); + RCHECK(frame.ReadBits(2, &layer_)); + RCHECK(layer_ != kMpeg1L_INV); + RCHECK(frame.ReadBits(1, &protection_absent_)); + + uint8_t btr_idx; + RCHECK(frame.ReadBits(4, &btr_idx)); + RCHECK(btr_idx > 0); + bitrate_ = Mpeg1BitRate(btr_idx, version_, layer_); + + uint8_t sr_idx; + RCHECK(frame.ReadBits(2, &sr_idx)); + RCHECK(sr_idx < 0b11); + sample_rate_ = Mpeg1SampleRate(sr_idx, version_); + + RCHECK(frame.ReadBits(1, &padded_)); + // Skip private stream bit. + RCHECK(frame.SkipBits(1)); + + RCHECK(frame.ReadBits(2, &channel_mode_)); + // Skip Mode extension + RCHECK(frame.SkipBits(2)); + // Skip copyright, origination and emphasis info. + RCHECK(frame.SkipBits(4)); + + return true; +} + +size_t Mpeg1Header::GetHeaderSize() const { + // Unlike ADTS, for MP3, the whole frame is included in the media sample, so + // return 0 header size. + return 0; +} + +size_t Mpeg1Header::GetFrameSize() const { + return Mpeg1FrameSize(layer_, bitrate_, sample_rate_, padded_); +} + +size_t Mpeg1Header::GetFrameSizeWithoutParsing(const uint8_t* data, + size_t num_bytes) const { + DCHECK_GT(num_bytes, static_cast(2)); + uint8_t version = (data[1] & 0b00011000) >> 3; + uint8_t layer = (data[1] & 0b00000110) >> 1; + uint8_t btr_idx = (data[2] & 0b11110000) >> 4; + uint8_t sr_idx = (data[2] & 0b00001100) >> 2; + uint8_t padded = (data[2] & 0b00000010) >> 1; + + if ((version == kMpeg1V_INV) || (layer == kMpeg1L_INV) || (btr_idx == 0) || + (sr_idx == 0b11)) + return 0; + + uint32_t bitrate = Mpeg1BitRate(btr_idx, version, layer); + uint32_t samplerate = Mpeg1SampleRate(sr_idx, version); + return Mpeg1FrameSize(layer, bitrate, samplerate, padded); +} + +void Mpeg1Header::GetAudioSpecificConfig(std::vector* buffer) const { + // The following conversion table is extracted from ISO 14496 Part 3 - + // Table 1.16 - Sampling Frequency Index. + static const size_t kConfigFrequencyTable[] = { + 96000, 88200, 64000, 48000, 44100, 32000, 24000, + 22050, 16000, 12000, 11025, 8000, 7350}; + static const size_t kConfigFrequencyTableSize = + arraysize(kConfigFrequencyTable); + uint8_t cft_idx; + + for (cft_idx = 0; cft_idx < kConfigFrequencyTableSize; cft_idx++) + if (sample_rate_ == kConfigFrequencyTable[cft_idx]) + break; + + DCHECK(buffer); + buffer->clear(); + BitWriter config(buffer); + config.WriteBits(GetObjectType(), 5); + config.WriteBits(cft_idx, 4); + /* + * NOTE: Number of channels matches channel_configuration index, + * since mpeg1 audio has only 1 or 2 channels + */ + config.WriteBits(GetNumChannels(), 4); + config.Flush(); +} + +uint8_t Mpeg1Header::GetObjectType() const { + /* + * ISO14496-3:2009 Table 1.17 - Audio Object Types + */ + if (layer_ == kMpeg1L_1) + return 32; + if (layer_ == kMpeg1L_2) + return 33; + + DCHECK_EQ(layer_, kMpeg1L_3); + return 34; +} + +uint32_t Mpeg1Header::GetSamplingFrequency() const { + return sample_rate_; +} + +uint8_t Mpeg1Header::GetNumChannels() const { + if (channel_mode_ == 0b11) + return 1; + return 2; +} + +} // namespace mp2t +} // namespace media +} // namespace shaka diff --git a/packager/media/formats/mp2t/mpeg1_header.h b/packager/media/formats/mp2t/mpeg1_header.h new file mode 100644 index 0000000000..ca3bd5ba5c --- /dev/null +++ b/packager/media/formats/mp2t/mpeg1_header.h @@ -0,0 +1,56 @@ +#ifndef PACKAGER_MEDIA_FORMATS_MP2T_MPEG1_HEADER_H_ +#define PACKAGER_MEDIA_FORMATS_MP2T_MPEG1_HEADER_H_ + +#include + +#include + +#include "packager/media/formats/mp2t/audio_header.h" + +namespace shaka { +namespace media { +namespace mp2t { +/// Class which parses Mpeg1 audio frame (header / metadata) and synthesizes +/// AudioSpecificConfig from audio frame content. +/// +/// See https://www.datavoyage.com/mpgscript/mpeghdr.htm +class Mpeg1Header : public AudioHeader { + public: + Mpeg1Header() = default; + ~Mpeg1Header() override = default; + + /// @name AudioHeader implementation overrides. + /// @{ + bool IsSyncWord(const uint8_t* buf) const override; + size_t GetMinFrameSize() const override; + size_t GetSamplesPerFrame() const override; + bool Parse(const uint8_t* mpeg1_frame, size_t mpeg1_frame_size) override; + size_t GetHeaderSize() const override; + size_t GetFrameSize() const override; + size_t GetFrameSizeWithoutParsing(const uint8_t* data, + size_t num_bytes) const override; + void GetAudioSpecificConfig(std::vector* buffer) const override; + uint8_t GetObjectType() const override; + uint32_t GetSamplingFrequency() const override; + uint8_t GetNumChannels() const override; + /// @} + + private: + Mpeg1Header(const Mpeg1Header&) = delete; + Mpeg1Header& operator=(const Mpeg1Header&) = delete; + + uint8_t version_ = 0; + uint8_t layer_ = 0; + uint8_t protection_absent_ = 0; + + uint32_t bitrate_ = 0; + uint32_t sample_rate_ = 0; /* in hz */ + uint8_t padded_ = 0; + uint8_t channel_mode_ = 0; +}; + +} // namespace mp2t +} // namespace media +} // namespace shaka + +#endif // PACKAGER_MEDIA_FORMATS_MP2T_MPEG1_HEADER_H_ diff --git a/packager/media/formats/mp2t/mpeg1_header_unittest.cc b/packager/media/formats/mp2t/mpeg1_header_unittest.cc new file mode 100644 index 0000000000..9683855d88 --- /dev/null +++ b/packager/media/formats/mp2t/mpeg1_header_unittest.cc @@ -0,0 +1,100 @@ +#include +#include + +#include "packager/base/logging.h" +#include "packager/base/strings/string_number_conversions.h" +#include "packager/media/formats/mp2t/mpeg1_header.h" + +using ::testing::ElementsAreArray; + +namespace { + +const char kValidMp3SyncByte[] = "FFFD800444333332114322"; + +const char kInvalidMp3SyncByte_1[] = "F00150802EFFFB80044433"; + +const char kInvalidMp3SyncByte_2[] = "FF8050802EDF"; + +const char kInvalidMp3SyncByte_3[] = "FF8050802EDFFF"; + +const char kValidMp3Frame[] = "ffFD800444333332114322"; + +const char kInvalidMp3FrameBadVersion[] = "FFE8800444333332114322"; + +const char kInvalidMp3FrameBadLayer[] = "FFF9800444333332114322"; + +const char kInvalidMp3FrameBadBitrate[] = "FFFD000444333332114322"; + +const char kInvalidMp3FrameBadSamepleRate[] = "FFFD8C0444333332114322"; + +} // anonymous namespace + +namespace shaka { +namespace media { +namespace mp2t { + +class Mpeg1HeaderTest : public testing::Test { + public: + void SetUp() override { + ASSERT_TRUE(base::HexStringToBytes(kValidMp3SyncByte, &sync_valid_)); + ASSERT_TRUE(base::HexStringToBytes(kInvalidMp3SyncByte_1, &sync_inv_1_)); + ASSERT_TRUE(base::HexStringToBytes(kInvalidMp3SyncByte_2, &sync_inv_2_)); + ASSERT_TRUE(base::HexStringToBytes(kInvalidMp3SyncByte_3, &sync_inv_3_)); + + ASSERT_TRUE(base::HexStringToBytes(kValidMp3Frame, &frame_valid_)); + ASSERT_TRUE( + base::HexStringToBytes(kInvalidMp3FrameBadVersion, &frame_inv_1_)); + ASSERT_TRUE( + base::HexStringToBytes(kInvalidMp3FrameBadLayer, &frame_inv_2_)); + ASSERT_TRUE( + base::HexStringToBytes(kInvalidMp3FrameBadBitrate, &frame_inv_3_)); + ASSERT_TRUE( + base::HexStringToBytes(kInvalidMp3FrameBadSamepleRate, &frame_inv_4_)); + } + + protected: + std::vector sync_valid_; + std::vector sync_inv_1_; + std::vector sync_inv_2_; + std::vector sync_inv_3_; + + std::vector frame_valid_; + std::vector frame_inv_1_; + std::vector frame_inv_2_; + std::vector frame_inv_3_; + std::vector frame_inv_4_; +}; + +TEST_F(Mpeg1HeaderTest, SyncBytes) { + Mpeg1Header mpeg1_header; + + ASSERT_TRUE(mpeg1_header.IsSyncWord(sync_valid_.data())); + + ASSERT_FALSE(mpeg1_header.IsSyncWord(sync_inv_1_.data())); + ASSERT_FALSE(mpeg1_header.IsSyncWord(sync_inv_2_.data())); + ASSERT_FALSE(mpeg1_header.IsSyncWord(sync_inv_3_.data())); +} + +TEST_F(Mpeg1HeaderTest, Parsing) { + Mpeg1Header mpeg1_header; + + // Success parsing + EXPECT_EQ(static_cast(417), + mpeg1_header.GetFrameSizeWithoutParsing(frame_valid_.data(), + frame_valid_.size())); + EXPECT_TRUE(mpeg1_header.Parse(frame_valid_.data(), frame_valid_.size())); + EXPECT_EQ(static_cast(417), mpeg1_header.GetFrameSize()); + EXPECT_EQ(static_cast(44100), mpeg1_header.GetSamplingFrequency()); + EXPECT_EQ(static_cast(1152), mpeg1_header.GetSamplesPerFrame()); + EXPECT_EQ(2, mpeg1_header.GetNumChannels()); + + // Failed parsing + EXPECT_FALSE(mpeg1_header.Parse(frame_inv_1_.data(), frame_inv_1_.size())); + EXPECT_FALSE(mpeg1_header.Parse(frame_inv_2_.data(), frame_inv_2_.size())); + EXPECT_FALSE(mpeg1_header.Parse(frame_inv_3_.data(), frame_inv_3_.size())); + EXPECT_FALSE(mpeg1_header.Parse(frame_inv_4_.data(), frame_inv_4_.size())); +} + +} // Namespace mp2t +} // namespace media +} // namespace shaka diff --git a/packager/media/formats/mp2t/pes_packet_generator.cc b/packager/media/formats/mp2t/pes_packet_generator.cc index 68b09592ea..facaff2638 100644 --- a/packager/media/formats/mp2t/pes_packet_generator.cc +++ b/packager/media/formats/mp2t/pes_packet_generator.cc @@ -63,9 +63,10 @@ bool PesPacketGenerator::Initialize(const StreamInfo& stream_info) { return adts_converter_->Parse(audio_stream_info.codec_config()); } if (audio_stream_info.codec() == Codec::kCodecAC3 || - audio_stream_info.codec() == Codec::kCodecEAC3) { + audio_stream_info.codec() == Codec::kCodecEAC3 || + audio_stream_info.codec() == Codec::kCodecMP3) { audio_stream_id_ = kAc3AudioStreamId; - // No converter needed for AC3 and E-AC3. + // No converter needed for AC3, E-AC3 and MP3. return true; } NOTIMPLEMENTED() << "Audio codec " << audio_stream_info.codec() diff --git a/packager/media/formats/mp2t/program_map_table_writer.cc b/packager/media/formats/mp2t/program_map_table_writer.cc index 83b1e1f9a0..2326121d53 100644 --- a/packager/media/formats/mp2t/program_map_table_writer.cc +++ b/packager/media/formats/mp2t/program_map_table_writer.cc @@ -261,6 +261,9 @@ bool ProgramMapTableWriter::ClearSegmentPmt(BufferWriter* writer) { case kCodecAAC: stream_type = TsStreamType::kAdtsAac; break; + case kCodecMP3: + stream_type = TsStreamType::kMpeg1Audio; + break; case kCodecAC3: stream_type = TsStreamType::kAc3; break; @@ -314,6 +317,9 @@ bool AudioProgramMapTableWriter::WriteDescriptors( case kCodecAAC: fourcc = FOURCC_aacd; break; + case kCodecMP3: + fourcc = FOURCC_mp3a; + break; case kCodecAC3: fourcc = FOURCC_ac3d; break; diff --git a/packager/packager.cc b/packager/packager.cc index 259453d37d..18671b0e34 100644 --- a/packager/packager.cc +++ b/packager/packager.cc @@ -230,7 +230,8 @@ Status ValidateStreamDescriptor(bool dump_stream_info, "descriptors 'output' or 'init_segment' are not allowed."); } } else if (output_format == CONTAINER_WEBVTT || - output_format == CONTAINER_AAC || output_format == CONTAINER_AC3 || + output_format == CONTAINER_AAC || output_format == CONTAINER_MP3 || + output_format == CONTAINER_AC3 || output_format == CONTAINER_EAC3) { // There is no need for an init segment when outputting because there is no // initialization data. @@ -807,6 +808,7 @@ Status CreateAllJobs(const std::vector& stream_descriptors, switch (GetOutputFormat(stream)) { case CONTAINER_MPEG2TS: case CONTAINER_AAC: + case CONTAINER_MP3: case CONTAINER_AC3: case CONTAINER_EAC3: has_transport_audio_video_streams = true;