Add PES packet related classes
- Define PesPacket class. - PesPacketGenerator creates PesPackets from samples. Change-Id: Icfd3656b498e0075f83ff3c789f95658f98c6144
This commit is contained in:
parent
40e1cc87d1
commit
cfd782ff40
|
@ -18,10 +18,11 @@ namespace media {
|
||||||
|
|
||||||
class VideoStreamInfo;
|
class VideoStreamInfo;
|
||||||
|
|
||||||
|
// Methods are virtual for mocking.
|
||||||
class NalUnitToByteStreamConverter {
|
class NalUnitToByteStreamConverter {
|
||||||
public:
|
public:
|
||||||
NalUnitToByteStreamConverter();
|
NalUnitToByteStreamConverter();
|
||||||
~NalUnitToByteStreamConverter();
|
virtual ~NalUnitToByteStreamConverter();
|
||||||
|
|
||||||
/// This must be called before calling other methods.
|
/// This must be called before calling other methods.
|
||||||
/// @param decoder_configuration_data is the pointer to a decoder config data.
|
/// @param decoder_configuration_data is the pointer to a decoder config data.
|
||||||
|
@ -31,9 +32,9 @@ class NalUnitToByteStreamConverter {
|
||||||
/// passed to ConvertUnitToByteStream() should be escaped with
|
/// passed to ConvertUnitToByteStream() should be escaped with
|
||||||
/// emulation prevention byte.
|
/// emulation prevention byte.
|
||||||
/// @return true on success, false otherwise.
|
/// @return true on success, false otherwise.
|
||||||
bool Initialize(const uint8_t* decoder_configuration_data,
|
virtual bool Initialize(const uint8_t* decoder_configuration_data,
|
||||||
size_t decoder_configuration_data_size,
|
size_t decoder_configuration_data_size,
|
||||||
bool escape_data);
|
bool escape_data);
|
||||||
|
|
||||||
/// Converts unit stream to byte stream using the data passed to Initialize().
|
/// Converts unit stream to byte stream using the data passed to Initialize().
|
||||||
/// The method will function correctly even if @a sample is encrypted using
|
/// The method will function correctly even if @a sample is encrypted using
|
||||||
|
@ -42,9 +43,10 @@ class NalUnitToByteStreamConverter {
|
||||||
/// @param sample_size is the size of @a sample.
|
/// @param sample_size is the size of @a sample.
|
||||||
/// @param output is set to the the converted sample, on success.
|
/// @param output is set to the the converted sample, on success.
|
||||||
/// @return true on success, false otherwise.
|
/// @return true on success, false otherwise.
|
||||||
bool ConvertUnitToByteStream(const uint8_t* sample, size_t sample_size,
|
virtual bool ConvertUnitToByteStream(const uint8_t* sample,
|
||||||
bool is_key_frame,
|
size_t sample_size,
|
||||||
std::vector<uint8_t>* output);
|
bool is_key_frame,
|
||||||
|
std::vector<uint8_t>* output);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class NalUnitToByteStreamConverterTest;
|
friend class NalUnitToByteStreamConverterTest;
|
||||||
|
|
|
@ -15,13 +15,17 @@
|
||||||
'sources': [
|
'sources': [
|
||||||
'adts_header.cc',
|
'adts_header.cc',
|
||||||
'adts_header.h',
|
'adts_header.h',
|
||||||
'es_parser.h',
|
|
||||||
'es_parser_adts.cc',
|
'es_parser_adts.cc',
|
||||||
'es_parser_adts.h',
|
'es_parser_adts.h',
|
||||||
'es_parser_h264.cc',
|
'es_parser_h264.cc',
|
||||||
'es_parser_h264.h',
|
'es_parser_h264.h',
|
||||||
|
'es_parser.h',
|
||||||
'mp2t_media_parser.cc',
|
'mp2t_media_parser.cc',
|
||||||
'mp2t_media_parser.h',
|
'mp2t_media_parser.h',
|
||||||
|
'pes_packet_generator.cc',
|
||||||
|
'pes_packet_generator.h',
|
||||||
|
'pes_packet.cc',
|
||||||
|
'pes_packet.h',
|
||||||
'ts_packet.cc',
|
'ts_packet.cc',
|
||||||
'ts_packet.h',
|
'ts_packet.h',
|
||||||
'ts_section_pat.cc',
|
'ts_section_pat.cc',
|
||||||
|
@ -35,6 +39,9 @@
|
||||||
],
|
],
|
||||||
'dependencies': [
|
'dependencies': [
|
||||||
'../../base/media_base.gyp:media_base',
|
'../../base/media_base.gyp:media_base',
|
||||||
|
# TODO(rkuroiwa): AACAudioSpecificConfig is used to create ADTS.
|
||||||
|
# Break this dependency on mp4 by moving it to media/filters.
|
||||||
|
'../../formats/mp4/mp4.gyp:mp4',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -44,6 +51,7 @@
|
||||||
'adts_header_unittest.cc',
|
'adts_header_unittest.cc',
|
||||||
'es_parser_h264_unittest.cc',
|
'es_parser_h264_unittest.cc',
|
||||||
'mp2t_media_parser_unittest.cc',
|
'mp2t_media_parser_unittest.cc',
|
||||||
|
'pes_packet_generator_unittest.cc',
|
||||||
],
|
],
|
||||||
'dependencies': [
|
'dependencies': [
|
||||||
'../../../testing/gtest.gyp:gtest',
|
'../../../testing/gtest.gyp:gtest',
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright 2016 Google Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file or at
|
||||||
|
// https://developers.google.com/open-source/licenses/bsd
|
||||||
|
|
||||||
|
#include "packager/media/formats/mp2t/pes_packet.h"
|
||||||
|
|
||||||
|
namespace edash_packager {
|
||||||
|
namespace media {
|
||||||
|
namespace mp2t {
|
||||||
|
|
||||||
|
PesPacket::PesPacket() {}
|
||||||
|
PesPacket::~PesPacket() {}
|
||||||
|
|
||||||
|
} // namespace mp2t
|
||||||
|
} // namespace media
|
||||||
|
} // namespace edash_packager
|
|
@ -0,0 +1,79 @@
|
||||||
|
// Copyright 2016 Google Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file or at
|
||||||
|
// https://developers.google.com/open-source/licenses/bsd
|
||||||
|
|
||||||
|
#ifndef PACKAGER_MEDIA_FORMATS_MP2T_PES_PACKET_H_
|
||||||
|
#define PACKAGER_MEDIA_FORMATS_MP2T_PES_PACKET_H_
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "packager/base/macros.h"
|
||||||
|
|
||||||
|
namespace edash_packager {
|
||||||
|
namespace media {
|
||||||
|
namespace mp2t {
|
||||||
|
|
||||||
|
/// Class that carries PES packet information.
|
||||||
|
class PesPacket {
|
||||||
|
public:
|
||||||
|
PesPacket();
|
||||||
|
~PesPacket();
|
||||||
|
|
||||||
|
/// @return the stream ID of the data that's carried by the PES packete.
|
||||||
|
uint8_t stream_id() const { return stream_id_; }
|
||||||
|
/// @param stream_id is used to set the stream ID.
|
||||||
|
void set_stream_id(uint8_t stream_id) { stream_id_ = stream_id; }
|
||||||
|
|
||||||
|
/// @return true if dts has been set.
|
||||||
|
bool has_dts() const { return dts_ < 0; }
|
||||||
|
/// @return true if pts has been set.
|
||||||
|
bool has_pts() const { return pts_ < 0; }
|
||||||
|
|
||||||
|
/// @return dts.
|
||||||
|
int64_t dts() const { return dts_; }
|
||||||
|
/// @param dts is the dts for this PES packet.
|
||||||
|
void set_dts(int64_t dts) {
|
||||||
|
dts_ = dts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @return pts.
|
||||||
|
int64_t pts() const { return pts_; }
|
||||||
|
/// @param pts is the pts for this PES packet.
|
||||||
|
void set_pts(int64_t pts) {
|
||||||
|
pts_ = pts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Duration is not really part of PES but its here to calculate stream's
|
||||||
|
/// duration.
|
||||||
|
/// @return duration of this PES in timescale.
|
||||||
|
int64_t duration() const { return duration_; }
|
||||||
|
/// @param duration of this PES.
|
||||||
|
void set_duration(int64_t duration) { duration_ = duration; }
|
||||||
|
|
||||||
|
/// @return data carried by this PES, the payload.
|
||||||
|
const std::vector<uint8_t>& data() const { return data_; }
|
||||||
|
/// @return mutable data for this PES.
|
||||||
|
std::vector<uint8_t>* mutable_data() { return &data_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t stream_id_ = 0;
|
||||||
|
|
||||||
|
// These values mean "not set" when the value is less than 0.
|
||||||
|
int64_t dts_ = -1;
|
||||||
|
int64_t pts_ = -1;
|
||||||
|
|
||||||
|
int64_t duration_ = 0;
|
||||||
|
|
||||||
|
std::vector<uint8_t> data_;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(PesPacket);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace mp2t
|
||||||
|
} // namespace media
|
||||||
|
} // namespace edash_packager
|
||||||
|
|
||||||
|
#endif // PACKAGER_MEDIA_FORMATS_MP2T_PES_PACKET_H_
|
|
@ -0,0 +1,115 @@
|
||||||
|
// Copyright 2016 Google Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file or at
|
||||||
|
// https://developers.google.com/open-source/licenses/bsd
|
||||||
|
|
||||||
|
#include "packager/media/formats/mp2t/pes_packet_generator.h"
|
||||||
|
|
||||||
|
#include "packager/media/base/audio_stream_info.h"
|
||||||
|
#include "packager/media/base/media_sample.h"
|
||||||
|
#include "packager/media/base/video_stream_info.h"
|
||||||
|
#include "packager/media/filters/nal_unit_to_byte_stream_converter.h"
|
||||||
|
#include "packager/media/formats/mp2t/pes_packet.h"
|
||||||
|
#include "packager/media/formats/mp4/aac_audio_specific_config.h"
|
||||||
|
|
||||||
|
namespace edash_packager {
|
||||||
|
namespace media {
|
||||||
|
namespace mp2t {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
const bool kEscapeData = true;
|
||||||
|
const uint8_t kVideoStreamId = 0xe0;
|
||||||
|
const uint8_t kAudioStreamId = 0xc0;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
PesPacketGenerator::PesPacketGenerator() {}
|
||||||
|
PesPacketGenerator::~PesPacketGenerator() {}
|
||||||
|
|
||||||
|
bool PesPacketGenerator::Initialize(const StreamInfo& stream_info) {
|
||||||
|
pes_packets_.clear();
|
||||||
|
stream_type_ = stream_info.stream_type();
|
||||||
|
|
||||||
|
if (stream_type_ == kStreamVideo) {
|
||||||
|
const VideoStreamInfo& video_stream_info =
|
||||||
|
static_cast<const VideoStreamInfo&>(stream_info);
|
||||||
|
if (video_stream_info.codec() != VideoCodec::kCodecH264) {
|
||||||
|
NOTIMPLEMENTED() << "Video codec " << video_stream_info.codec()
|
||||||
|
<< " is not supported.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
timescale_scale_ = 90000.0 / video_stream_info.time_scale();
|
||||||
|
converter_.reset(new NalUnitToByteStreamConverter());
|
||||||
|
return converter_->Initialize(video_stream_info.extra_data().data(),
|
||||||
|
video_stream_info.extra_data().size(),
|
||||||
|
!kEscapeData);
|
||||||
|
} else if (stream_type_ == kStreamAudio) {
|
||||||
|
const AudioStreamInfo& audio_stream_info =
|
||||||
|
static_cast<const AudioStreamInfo&>(stream_info);
|
||||||
|
if (audio_stream_info.codec() != AudioCodec::kCodecAAC) {
|
||||||
|
NOTIMPLEMENTED() << "Audio codec " << audio_stream_info.codec()
|
||||||
|
<< " is not supported yet.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
timescale_scale_ = 90000.0 / audio_stream_info.time_scale();
|
||||||
|
adts_converter_.reset(new mp4::AACAudioSpecificConfig());
|
||||||
|
return adts_converter_->Parse(audio_stream_info.extra_data());
|
||||||
|
}
|
||||||
|
|
||||||
|
NOTIMPLEMENTED() << "Stream type: " << stream_type_ << " not implemented.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PesPacketGenerator::PushSample(scoped_refptr<MediaSample> sample) {
|
||||||
|
if (!current_processing_pes_)
|
||||||
|
current_processing_pes_.reset(new PesPacket());
|
||||||
|
|
||||||
|
current_processing_pes_->set_duration(sample->duration());
|
||||||
|
current_processing_pes_->set_pts(timescale_scale_ * sample->pts());
|
||||||
|
current_processing_pes_->set_dts(timescale_scale_ * sample->dts());
|
||||||
|
if (stream_type_ == kStreamVideo) {
|
||||||
|
DCHECK(converter_);
|
||||||
|
if (!converter_->ConvertUnitToByteStream(
|
||||||
|
sample->data(), sample->data_size(), sample->is_key_frame(),
|
||||||
|
current_processing_pes_->mutable_data())) {
|
||||||
|
LOG(ERROR) << "Failed to convert sample to byte stream.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
current_processing_pes_->set_stream_id(kVideoStreamId);
|
||||||
|
pes_packets_.push_back(current_processing_pes_.Pass());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
DCHECK_EQ(stream_type_, kStreamAudio);
|
||||||
|
DCHECK(adts_converter_);
|
||||||
|
|
||||||
|
std::vector<uint8_t> aac_frame(sample->data(),
|
||||||
|
sample->data() + sample->data_size());
|
||||||
|
if (!adts_converter_->ConvertToADTS(&aac_frame))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// TODO(rkuriowa): Put multiple samples in the PES packet to reduce # of PES
|
||||||
|
// packets.
|
||||||
|
current_processing_pes_->mutable_data()->swap(aac_frame);
|
||||||
|
current_processing_pes_->set_stream_id(kAudioStreamId);
|
||||||
|
pes_packets_.push_back(current_processing_pes_.Pass());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t PesPacketGenerator::NumberOfReadyPesPackets() {
|
||||||
|
return pes_packets_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
scoped_ptr<PesPacket> PesPacketGenerator::GetNextPesPacket() {
|
||||||
|
DCHECK(!pes_packets_.empty());
|
||||||
|
scoped_ptr<PesPacket> pes = pes_packets_.front().Pass();
|
||||||
|
pes_packets_.pop_front();
|
||||||
|
return pes.Pass();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PesPacketGenerator::Flush() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mp2t
|
||||||
|
} // namespace media
|
||||||
|
} // namespace edash_packager
|
|
@ -0,0 +1,89 @@
|
||||||
|
// Copyright 2016 Google Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file or at
|
||||||
|
// https://developers.google.com/open-source/licenses/bsd
|
||||||
|
|
||||||
|
#ifndef PACKAGER_MEDIA_FORMATS_MP2T_PES_PACKET_GENERATOR_H_
|
||||||
|
#define PACKAGER_MEDIA_FORMATS_MP2T_PES_PACKET_GENERATOR_H_
|
||||||
|
|
||||||
|
#include <list>
|
||||||
|
|
||||||
|
#include "packager/base/memory/scoped_ptr.h"
|
||||||
|
#include "packager/media/base/media_sample.h"
|
||||||
|
#include "packager/media/base/stream_info.h"
|
||||||
|
|
||||||
|
namespace edash_packager {
|
||||||
|
namespace media {
|
||||||
|
|
||||||
|
class NalUnitToByteStreamConverter;
|
||||||
|
class StreamInfo;
|
||||||
|
|
||||||
|
namespace mp4 {
|
||||||
|
class AACAudioSpecificConfig;
|
||||||
|
} // namespace mp4
|
||||||
|
|
||||||
|
namespace mp2t {
|
||||||
|
|
||||||
|
class PesPacket;
|
||||||
|
|
||||||
|
/// Generates PesPackets from MediaSamples.
|
||||||
|
class PesPacketGenerator {
|
||||||
|
public:
|
||||||
|
PesPacketGenerator();
|
||||||
|
~PesPacketGenerator();
|
||||||
|
|
||||||
|
/// Initialize the object. This clears the internal state first so any
|
||||||
|
/// PesPackets that have not been flushed will be lost.
|
||||||
|
/// @param stream is the stream info for the elementary stream that will be
|
||||||
|
/// added via PushSample().
|
||||||
|
/// @return true on success, false otherwise.
|
||||||
|
bool Initialize(const StreamInfo& stream);
|
||||||
|
|
||||||
|
/// Add a sample to the generator. This does not necessarily increase
|
||||||
|
/// NumberOfReadyPesPackets().
|
||||||
|
/// If this returns false, the object may end up in an undefined state.
|
||||||
|
/// @return true on success, false otherwise.
|
||||||
|
bool PushSample(scoped_refptr<MediaSample> sample);
|
||||||
|
|
||||||
|
/// @return The number of PES packets that are ready to be consumed.
|
||||||
|
size_t NumberOfReadyPesPackets();
|
||||||
|
|
||||||
|
/// Removes the next PES packet from the stream and returns it. Must have at
|
||||||
|
/// least one packet ready.
|
||||||
|
/// @return Next PES packet that is ready.
|
||||||
|
scoped_ptr<PesPacket> GetNextPesPacket();
|
||||||
|
|
||||||
|
/// Flush the object. This may create more PesPackets with the stored
|
||||||
|
/// samples.
|
||||||
|
/// It is safe to call NumberOfReadyPesPackets() and GetNextPesPacket() after
|
||||||
|
/// this.
|
||||||
|
/// @return true on success, false otherwise.
|
||||||
|
bool Flush();
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class PesPacketGeneratorTest;
|
||||||
|
|
||||||
|
StreamType stream_type_;
|
||||||
|
|
||||||
|
// Calculated by 90000 / input stream's timescale. This is used to scale the
|
||||||
|
// timestamps.
|
||||||
|
double timescale_scale_ = 0.0;
|
||||||
|
|
||||||
|
scoped_ptr<NalUnitToByteStreamConverter> converter_;
|
||||||
|
scoped_ptr<mp4::AACAudioSpecificConfig> adts_converter_;
|
||||||
|
|
||||||
|
// This is the PES packet that this object is currently working on.
|
||||||
|
// This can be used to create a PES from multiple audio samples.
|
||||||
|
scoped_ptr<PesPacket> current_processing_pes_;
|
||||||
|
|
||||||
|
std::list<scoped_ptr<PesPacket>> pes_packets_;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(PesPacketGenerator);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace mp2t
|
||||||
|
} // namespace media
|
||||||
|
} // namespace edash_packager
|
||||||
|
|
||||||
|
#endif // PACKAGER_MEDIA_FORMATS_MP2T_PES_PACKET_GENERATOR_H_
|
|
@ -0,0 +1,323 @@
|
||||||
|
// Copyright 2016 Google Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file or at
|
||||||
|
// https://developers.google.com/open-source/licenses/bsd
|
||||||
|
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include "packager/media/base/audio_stream_info.h"
|
||||||
|
#include "packager/media/base/media_sample.h"
|
||||||
|
#include "packager/media/base/text_stream_info.h"
|
||||||
|
#include "packager/media/base/video_stream_info.h"
|
||||||
|
#include "packager/media/filters/nal_unit_to_byte_stream_converter.h"
|
||||||
|
#include "packager/media/formats/mp2t/pes_packet.h"
|
||||||
|
#include "packager/media/formats/mp2t/pes_packet_generator.h"
|
||||||
|
#include "packager/media/formats/mp4/aac_audio_specific_config.h"
|
||||||
|
|
||||||
|
namespace edash_packager {
|
||||||
|
namespace media {
|
||||||
|
namespace mp2t {
|
||||||
|
|
||||||
|
using ::testing::_;
|
||||||
|
using ::testing::DoAll;
|
||||||
|
using ::testing::SetArgPointee;
|
||||||
|
using ::testing::Return;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Bogus data for testing.
|
||||||
|
const uint8_t kAnyData[] = {
|
||||||
|
0x56, 0x87, 0x88, 0x33, 0x98, 0xAF, 0xE5,
|
||||||
|
};
|
||||||
|
|
||||||
|
const bool kIsKeyFrame = true;
|
||||||
|
|
||||||
|
// Only {Audio,Video}Codec and extra data matter for this test. Other values are
|
||||||
|
// bogus.
|
||||||
|
const VideoCodec kH264VideoCodec = VideoCodec::kCodecH264;
|
||||||
|
const AudioCodec kAacAudioCodec = AudioCodec::kCodecAAC;
|
||||||
|
|
||||||
|
// TODO(rkuroiwa): It might make sense to inject factory functions to create
|
||||||
|
// NalUnitToByteStreamConverter and AACAudioSpecificConfig so that these
|
||||||
|
// extra data don't need to be copy pasted from other tests.
|
||||||
|
const uint8_t kVideoExtraData[] = {
|
||||||
|
0x01, // configuration version (must be 1)
|
||||||
|
0x00, // AVCProfileIndication (bogus)
|
||||||
|
0x00, // profile_compatibility (bogus)
|
||||||
|
0x00, // AVCLevelIndication (bogus)
|
||||||
|
0xFF, // Length size minus 1 == 3
|
||||||
|
0xE1, // 1 sps.
|
||||||
|
0x00, 0x1D, // SPS length == 29
|
||||||
|
0x67, 0x64, 0x00, 0x1E, 0xAC, 0xD9, 0x40, 0xB4,
|
||||||
|
0x2F, 0xF9, 0x7F, 0xF0, 0x00, 0x80, 0x00, 0x91,
|
||||||
|
0x00, 0x00, 0x03, 0x03, 0xE9, 0x00, 0x00, 0xEA,
|
||||||
|
0x60, 0x0F, 0x16, 0x2D, 0x96,
|
||||||
|
0x01, // 1 pps.
|
||||||
|
0x00, 0x0A, // PPS length == 10
|
||||||
|
0x68, 0xFE, 0xFD, 0xFC, 0xFB, 0x11, 0x12, 0x13, 0x14, 0x15,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Basic profile.
|
||||||
|
const uint8_t kAudioExtraData[] = {0x12, 0x10};
|
||||||
|
|
||||||
|
const int kTrackId = 0;
|
||||||
|
const uint32_t kTimeScale = 90000;
|
||||||
|
const uint64_t kDuration = 180000;
|
||||||
|
const char kCodecString[] = "avc1";
|
||||||
|
const char kLanguage[] = "eng";
|
||||||
|
const uint32_t kWidth = 1280;
|
||||||
|
const uint32_t kHeight = 720;
|
||||||
|
const uint32_t kPixelWidth = 1;
|
||||||
|
const uint32_t kPixelHeight = 1;
|
||||||
|
const uint16_t kTrickPlayRate = 1;
|
||||||
|
const uint8_t kNaluLengthSize = 1;
|
||||||
|
const bool kIsEncrypted = false;
|
||||||
|
|
||||||
|
const uint8_t kSampleBits = 16;
|
||||||
|
const uint8_t kNumChannels = 2;
|
||||||
|
const uint32_t kSamplingFrequency = 44100;
|
||||||
|
const uint32_t kMaxBitrate = 320000;
|
||||||
|
const uint32_t kAverageBitrate = 256000;
|
||||||
|
|
||||||
|
class MockNalUnitToByteStreamConverter : public NalUnitToByteStreamConverter {
|
||||||
|
public:
|
||||||
|
MOCK_METHOD3(Initialize,
|
||||||
|
bool(const uint8_t* decoder_configuration_data,
|
||||||
|
size_t decoder_configuration_data_size,
|
||||||
|
bool escape_data));
|
||||||
|
MOCK_METHOD4(ConvertUnitToByteStream,
|
||||||
|
bool(const uint8_t* sample,
|
||||||
|
size_t sample_size,
|
||||||
|
bool is_key_frame,
|
||||||
|
std::vector<uint8_t>* output));
|
||||||
|
};
|
||||||
|
|
||||||
|
class MockAACAudioSpecificConfig : public mp4::AACAudioSpecificConfig {
|
||||||
|
public:
|
||||||
|
MOCK_METHOD1(Parse, bool(const std::vector<uint8_t>& data));
|
||||||
|
MOCK_CONST_METHOD1(ConvertToADTS, bool(std::vector<uint8_t>* buffer));
|
||||||
|
};
|
||||||
|
|
||||||
|
scoped_refptr<VideoStreamInfo> CreateVideoStreamInfo(VideoCodec codec) {
|
||||||
|
scoped_refptr<VideoStreamInfo> stream_info(new VideoStreamInfo(
|
||||||
|
kTrackId, kTimeScale, kDuration, codec, kCodecString, kLanguage,
|
||||||
|
kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate,
|
||||||
|
kNaluLengthSize, kVideoExtraData, arraysize(kVideoExtraData),
|
||||||
|
kIsEncrypted));
|
||||||
|
return stream_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
scoped_refptr<AudioStreamInfo> CreateAudioStreamInfo(AudioCodec codec) {
|
||||||
|
scoped_refptr<AudioStreamInfo> stream_info(new AudioStreamInfo(
|
||||||
|
kTrackId, kTimeScale, kDuration, codec, kCodecString,
|
||||||
|
kLanguage, kSampleBits, kNumChannels, kSamplingFrequency, kMaxBitrate,
|
||||||
|
kAverageBitrate, kAudioExtraData, arraysize(kAudioExtraData),
|
||||||
|
kIsEncrypted));
|
||||||
|
return stream_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
class PesPacketGeneratorTest : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
void UseMockNalUnitToByteStreamConverter(
|
||||||
|
scoped_ptr<MockNalUnitToByteStreamConverter>
|
||||||
|
mock_nal_unit_to_byte_stream_converter) {
|
||||||
|
generator_.converter_ = mock_nal_unit_to_byte_stream_converter.Pass();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UseMockAACAudioSpecificConfig(
|
||||||
|
scoped_ptr<MockAACAudioSpecificConfig> mock) {
|
||||||
|
generator_.adts_converter_ = mock.Pass();
|
||||||
|
}
|
||||||
|
|
||||||
|
PesPacketGenerator generator_;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(PesPacketGeneratorTest, InitializeVideo) {
|
||||||
|
scoped_refptr<VideoStreamInfo> stream_info(
|
||||||
|
CreateVideoStreamInfo(kH264VideoCodec));
|
||||||
|
EXPECT_TRUE(generator_.Initialize(*stream_info));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(PesPacketGeneratorTest, InitializeVideoNonH264) {
|
||||||
|
scoped_refptr<VideoStreamInfo> stream_info(
|
||||||
|
CreateVideoStreamInfo(VideoCodec::kCodecVP9));
|
||||||
|
EXPECT_FALSE(generator_.Initialize(*stream_info));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(PesPacketGeneratorTest, InitializeAudio) {
|
||||||
|
scoped_refptr<AudioStreamInfo> stream_info(
|
||||||
|
CreateAudioStreamInfo(kAacAudioCodec));
|
||||||
|
EXPECT_TRUE(generator_.Initialize(*stream_info));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(PesPacketGeneratorTest, InitializeAudioNonAac) {
|
||||||
|
scoped_refptr<AudioStreamInfo> stream_info(
|
||||||
|
CreateAudioStreamInfo(AudioCodec::kCodecOpus));
|
||||||
|
EXPECT_FALSE(generator_.Initialize(*stream_info));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text is not supported yet.
|
||||||
|
TEST_F(PesPacketGeneratorTest, InitializeTextInfo) {
|
||||||
|
scoped_refptr<TextStreamInfo> stream_info(
|
||||||
|
new TextStreamInfo(kTrackId, kTimeScale, kDuration, kCodecString,
|
||||||
|
kLanguage, std::string(), kWidth, kHeight));
|
||||||
|
EXPECT_FALSE(generator_.Initialize(*stream_info));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(PesPacketGeneratorTest, AddVideoSample) {
|
||||||
|
scoped_refptr<VideoStreamInfo> stream_info(
|
||||||
|
CreateVideoStreamInfo(kH264VideoCodec));
|
||||||
|
EXPECT_TRUE(generator_.Initialize(*stream_info));
|
||||||
|
EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets());
|
||||||
|
|
||||||
|
scoped_refptr<MediaSample> sample =
|
||||||
|
MediaSample::CopyFrom(kAnyData, arraysize(kAnyData), kIsKeyFrame);
|
||||||
|
const uint32_t kPts = 12345;
|
||||||
|
const uint32_t kDts = 12300;
|
||||||
|
sample->set_pts(kPts);
|
||||||
|
sample->set_dts(kDts);
|
||||||
|
|
||||||
|
std::vector<uint8_t> expected_data(kAnyData, kAnyData + arraysize(kAnyData));
|
||||||
|
|
||||||
|
scoped_ptr<MockNalUnitToByteStreamConverter> mock(
|
||||||
|
new MockNalUnitToByteStreamConverter());
|
||||||
|
EXPECT_CALL(*mock,
|
||||||
|
ConvertUnitToByteStream(_, arraysize(kAnyData), kIsKeyFrame, _))
|
||||||
|
.WillOnce(DoAll(SetArgPointee<3>(expected_data), Return(true)));
|
||||||
|
|
||||||
|
UseMockNalUnitToByteStreamConverter(mock.Pass());
|
||||||
|
|
||||||
|
EXPECT_TRUE(generator_.PushSample(sample));
|
||||||
|
EXPECT_EQ(1u, generator_.NumberOfReadyPesPackets());
|
||||||
|
scoped_ptr<PesPacket> pes_packet = generator_.GetNextPesPacket();
|
||||||
|
ASSERT_TRUE(pes_packet);
|
||||||
|
EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets());
|
||||||
|
|
||||||
|
EXPECT_EQ(0xe0, pes_packet->stream_id());
|
||||||
|
EXPECT_EQ(kPts, pes_packet->pts());
|
||||||
|
EXPECT_EQ(kDts, pes_packet->dts());
|
||||||
|
EXPECT_EQ(expected_data, pes_packet->data());
|
||||||
|
|
||||||
|
EXPECT_TRUE(generator_.Flush());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(PesPacketGeneratorTest, AddVideoSampleFailedToConvert) {
|
||||||
|
scoped_refptr<VideoStreamInfo> stream_info(
|
||||||
|
CreateVideoStreamInfo(kH264VideoCodec));
|
||||||
|
EXPECT_TRUE(generator_.Initialize(*stream_info));
|
||||||
|
EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets());
|
||||||
|
|
||||||
|
scoped_refptr<MediaSample> sample =
|
||||||
|
MediaSample::CopyFrom(kAnyData, arraysize(kAnyData), kIsKeyFrame);
|
||||||
|
|
||||||
|
std::vector<uint8_t> expected_data(kAnyData, kAnyData + arraysize(kAnyData));
|
||||||
|
scoped_ptr<MockNalUnitToByteStreamConverter> mock(
|
||||||
|
new MockNalUnitToByteStreamConverter());
|
||||||
|
EXPECT_CALL(*mock,
|
||||||
|
ConvertUnitToByteStream(_, arraysize(kAnyData), kIsKeyFrame, _))
|
||||||
|
.WillOnce(Return(false));
|
||||||
|
|
||||||
|
UseMockNalUnitToByteStreamConverter(mock.Pass());
|
||||||
|
|
||||||
|
EXPECT_FALSE(generator_.PushSample(sample));
|
||||||
|
EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets());
|
||||||
|
EXPECT_TRUE(generator_.Flush());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(PesPacketGeneratorTest, AddAudioSample) {
|
||||||
|
scoped_refptr<AudioStreamInfo> stream_info(
|
||||||
|
CreateAudioStreamInfo(kAacAudioCodec));
|
||||||
|
EXPECT_TRUE(generator_.Initialize(*stream_info));
|
||||||
|
EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets());
|
||||||
|
|
||||||
|
scoped_refptr<MediaSample> sample =
|
||||||
|
MediaSample::CopyFrom(kAnyData, arraysize(kAnyData), kIsKeyFrame);
|
||||||
|
|
||||||
|
std::vector<uint8_t> expected_data(kAnyData, kAnyData + arraysize(kAnyData));
|
||||||
|
|
||||||
|
scoped_ptr<MockAACAudioSpecificConfig> mock(new MockAACAudioSpecificConfig());
|
||||||
|
EXPECT_CALL(*mock, ConvertToADTS(_))
|
||||||
|
.WillOnce(DoAll(SetArgPointee<0>(expected_data), Return(true)));
|
||||||
|
|
||||||
|
UseMockAACAudioSpecificConfig(mock.Pass());
|
||||||
|
|
||||||
|
EXPECT_TRUE(generator_.PushSample(sample));
|
||||||
|
EXPECT_EQ(1u, generator_.NumberOfReadyPesPackets());
|
||||||
|
scoped_ptr<PesPacket> pes_packet = generator_.GetNextPesPacket();
|
||||||
|
ASSERT_TRUE(pes_packet);
|
||||||
|
EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets());
|
||||||
|
|
||||||
|
EXPECT_EQ(0xc0, pes_packet->stream_id());
|
||||||
|
EXPECT_EQ(expected_data, pes_packet->data());
|
||||||
|
|
||||||
|
EXPECT_TRUE(generator_.Flush());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(PesPacketGeneratorTest, AddAudioSampleFailedToConvert) {
|
||||||
|
scoped_refptr<AudioStreamInfo> stream_info(
|
||||||
|
CreateAudioStreamInfo(kAacAudioCodec));
|
||||||
|
EXPECT_TRUE(generator_.Initialize(*stream_info));
|
||||||
|
EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets());
|
||||||
|
|
||||||
|
scoped_refptr<MediaSample> sample =
|
||||||
|
MediaSample::CopyFrom(kAnyData, arraysize(kAnyData), kIsKeyFrame);
|
||||||
|
|
||||||
|
scoped_ptr<MockAACAudioSpecificConfig> mock(new MockAACAudioSpecificConfig());
|
||||||
|
EXPECT_CALL(*mock, ConvertToADTS(_)).WillOnce(Return(false));
|
||||||
|
|
||||||
|
UseMockAACAudioSpecificConfig(mock.Pass());
|
||||||
|
|
||||||
|
EXPECT_FALSE(generator_.PushSample(sample));
|
||||||
|
EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets());
|
||||||
|
EXPECT_TRUE(generator_.Flush());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because TS has to use 90000 as its timescale, make sure that the timestamps
|
||||||
|
// are scaled.
|
||||||
|
TEST_F(PesPacketGeneratorTest, TimeStampScaling) {
|
||||||
|
const uint32_t kTestTimescale = 1000;
|
||||||
|
scoped_refptr<VideoStreamInfo> stream_info(new VideoStreamInfo(
|
||||||
|
kTrackId, kTestTimescale, kDuration, kH264VideoCodec, kCodecString,
|
||||||
|
kLanguage, kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate,
|
||||||
|
kNaluLengthSize, kVideoExtraData, arraysize(kVideoExtraData),
|
||||||
|
kIsEncrypted));
|
||||||
|
EXPECT_TRUE(generator_.Initialize(*stream_info));
|
||||||
|
|
||||||
|
EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets());
|
||||||
|
|
||||||
|
scoped_refptr<MediaSample> sample =
|
||||||
|
MediaSample::CopyFrom(kAnyData, arraysize(kAnyData), kIsKeyFrame);
|
||||||
|
const uint32_t kPts = 5000;
|
||||||
|
const uint32_t kDts = 4000;
|
||||||
|
sample->set_pts(kPts);
|
||||||
|
sample->set_dts(kDts);
|
||||||
|
|
||||||
|
scoped_ptr<MockNalUnitToByteStreamConverter> mock(
|
||||||
|
new MockNalUnitToByteStreamConverter());
|
||||||
|
EXPECT_CALL(*mock,
|
||||||
|
ConvertUnitToByteStream(_, arraysize(kAnyData), kIsKeyFrame, _))
|
||||||
|
.WillOnce(Return(true));
|
||||||
|
|
||||||
|
UseMockNalUnitToByteStreamConverter(mock.Pass());
|
||||||
|
|
||||||
|
EXPECT_TRUE(generator_.PushSample(sample));
|
||||||
|
EXPECT_EQ(1u, generator_.NumberOfReadyPesPackets());
|
||||||
|
scoped_ptr<PesPacket> pes_packet = generator_.GetNextPesPacket();
|
||||||
|
ASSERT_TRUE(pes_packet);
|
||||||
|
EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets());
|
||||||
|
|
||||||
|
// Since 90000 (MPEG2 timescale) / 1000 (input timescale) is 90, the
|
||||||
|
// timestamps should be multipled by 90.
|
||||||
|
EXPECT_EQ(kPts * 90, pes_packet->pts());
|
||||||
|
EXPECT_EQ(kDts * 90, pes_packet->dts());
|
||||||
|
|
||||||
|
EXPECT_TRUE(generator_.Flush());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mp2t
|
||||||
|
} // namespace media
|
||||||
|
} // namespace edash_packager
|
|
@ -127,6 +127,31 @@ bool AACAudioSpecificConfig::Parse(const std::vector<uint8_t>& data) {
|
||||||
channel_config_ <= 7;
|
channel_config_ <= 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AACAudioSpecificConfig::ConvertToADTS(std::vector<uint8_t>* buffer) const {
|
||||||
|
size_t size = buffer->size() + kADTSHeaderSize;
|
||||||
|
|
||||||
|
DCHECK(audio_object_type_ >= 1 && audio_object_type_ <= 4 &&
|
||||||
|
frequency_index_ != 0xf && channel_config_ <= 7);
|
||||||
|
|
||||||
|
// ADTS header uses 13 bits for packet size.
|
||||||
|
if (size >= (1 << 13))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::vector<uint8_t>& adts = *buffer;
|
||||||
|
|
||||||
|
adts.insert(buffer->begin(), kADTSHeaderSize, 0);
|
||||||
|
adts[0] = 0xff;
|
||||||
|
adts[1] = 0xf1;
|
||||||
|
adts[2] = ((audio_object_type_ - 1) << 6) + (frequency_index_ << 2) +
|
||||||
|
(channel_config_ >> 2);
|
||||||
|
adts[3] = ((channel_config_ & 0x3) << 6) + (size >> 11);
|
||||||
|
adts[4] = (size & 0x7ff) >> 3;
|
||||||
|
adts[5] = ((size & 7) << 5) + 0x1f;
|
||||||
|
adts[6] = 0xfc;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t AACAudioSpecificConfig::GetOutputSamplesPerSecond(
|
uint32_t AACAudioSpecificConfig::GetOutputSamplesPerSecond(
|
||||||
bool sbr_in_mimetype) const {
|
bool sbr_in_mimetype) const {
|
||||||
if (extension_frequency_ > 0)
|
if (extension_frequency_ > 0)
|
||||||
|
@ -156,31 +181,6 @@ uint8_t AACAudioSpecificConfig::GetNumChannels(bool sbr_in_mimetype) const {
|
||||||
return num_channels_;
|
return num_channels_;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AACAudioSpecificConfig::ConvertToADTS(std::vector<uint8_t>* buffer) const {
|
|
||||||
size_t size = buffer->size() + kADTSHeaderSize;
|
|
||||||
|
|
||||||
DCHECK(audio_object_type_ >= 1 && audio_object_type_ <= 4 &&
|
|
||||||
frequency_index_ != 0xf && channel_config_ <= 7);
|
|
||||||
|
|
||||||
// ADTS header uses 13 bits for packet size.
|
|
||||||
if (size >= (1 << 13))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
std::vector<uint8_t>& adts = *buffer;
|
|
||||||
|
|
||||||
adts.insert(buffer->begin(), kADTSHeaderSize, 0);
|
|
||||||
adts[0] = 0xff;
|
|
||||||
adts[1] = 0xf1;
|
|
||||||
adts[2] = ((audio_object_type_ - 1) << 6) + (frequency_index_ << 2) +
|
|
||||||
(channel_config_ >> 2);
|
|
||||||
adts[3] = ((channel_config_ & 0x3) << 6) + (size >> 11);
|
|
||||||
adts[4] = (size & 0x7ff) >> 3;
|
|
||||||
adts[5] = ((size & 7) << 5) + 0x1f;
|
|
||||||
adts[6] = 0xfc;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Currently this function only support GASpecificConfig defined in
|
// Currently this function only support GASpecificConfig defined in
|
||||||
// ISO 14496 Part 3 Table 4.1 - Syntax of GASpecificConfig()
|
// ISO 14496 Part 3 Table 4.1 - Syntax of GASpecificConfig()
|
||||||
bool AACAudioSpecificConfig::SkipDecoderGASpecificConfig(BitReader* bit_reader) const {
|
bool AACAudioSpecificConfig::SkipDecoderGASpecificConfig(BitReader* bit_reader) const {
|
||||||
|
|
|
@ -17,6 +17,7 @@ class BitReader;
|
||||||
|
|
||||||
namespace mp4 {
|
namespace mp4 {
|
||||||
|
|
||||||
|
// Methods are virtual for mocking.
|
||||||
/// This class parses the AAC information from decoder specific information
|
/// This class parses the AAC information from decoder specific information
|
||||||
/// embedded in the @b esds box in an ISO BMFF file.
|
/// embedded in the @b esds box in an ISO BMFF file.
|
||||||
/// Please refer to ISO 14496 Part 3 Table 1.13 - Syntax of AudioSpecificConfig
|
/// Please refer to ISO 14496 Part 3 Table 1.13 - Syntax of AudioSpecificConfig
|
||||||
|
@ -24,7 +25,7 @@ namespace mp4 {
|
||||||
class AACAudioSpecificConfig {
|
class AACAudioSpecificConfig {
|
||||||
public:
|
public:
|
||||||
AACAudioSpecificConfig();
|
AACAudioSpecificConfig();
|
||||||
~AACAudioSpecificConfig();
|
virtual ~AACAudioSpecificConfig();
|
||||||
|
|
||||||
/// Parse the AAC config from decoder specific information embedded in an @b
|
/// Parse the AAC config from decoder specific information embedded in an @b
|
||||||
/// esds box. The function will parse the data and get the
|
/// esds box. The function will parse the data and get the
|
||||||
|
@ -32,7 +33,14 @@ class AACAudioSpecificConfig {
|
||||||
/// ElementaryStreamDescriptor to get audio stream configurations.
|
/// ElementaryStreamDescriptor to get audio stream configurations.
|
||||||
/// @param data contains decoder specific information from an @b esds box.
|
/// @param data contains decoder specific information from an @b esds box.
|
||||||
/// @return true if successful, false otherwise.
|
/// @return true if successful, false otherwise.
|
||||||
bool Parse(const std::vector<uint8_t>& data);
|
virtual bool Parse(const std::vector<uint8_t>& data);
|
||||||
|
|
||||||
|
/// Convert a raw AAC frame into an AAC frame with an ADTS header.
|
||||||
|
/// @param[in,out] buffer contains the raw AAC frame on input, and the
|
||||||
|
/// converted frame on output if successful; it is untouched
|
||||||
|
/// on failure.
|
||||||
|
/// @return true on success, false otherwise.
|
||||||
|
virtual bool ConvertToADTS(std::vector<uint8_t>* buffer) const;
|
||||||
|
|
||||||
/// @param sbr_in_mimetype indicates whether SBR mode is specified in the
|
/// @param sbr_in_mimetype indicates whether SBR mode is specified in the
|
||||||
/// mimetype, i.e. codecs parameter contains mp4a.40.5.
|
/// mimetype, i.e. codecs parameter contains mp4a.40.5.
|
||||||
|
@ -44,13 +52,6 @@ class AACAudioSpecificConfig {
|
||||||
/// @return Number of channels for the AAC stream.
|
/// @return Number of channels for the AAC stream.
|
||||||
uint8_t GetNumChannels(bool sbr_in_mimetype) const;
|
uint8_t GetNumChannels(bool sbr_in_mimetype) const;
|
||||||
|
|
||||||
/// Convert a raw AAC frame into an AAC frame with an ADTS header.
|
|
||||||
/// @param[in,out] buffer contains the raw AAC frame on input, and the
|
|
||||||
/// converted frame on output if successful; it is untouched
|
|
||||||
/// on failure.
|
|
||||||
/// @return true on success, false otherwise.
|
|
||||||
bool ConvertToADTS(std::vector<uint8_t>* buffer) const;
|
|
||||||
|
|
||||||
/// @return The audio object type for this AAC config.
|
/// @return The audio object type for this AAC config.
|
||||||
uint8_t audio_object_type() const { return audio_object_type_; }
|
uint8_t audio_object_type() const { return audio_object_type_; }
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue