Support Dolby audio AC3 in ISO BMFF (Part 1)
- Box definitions for box type DAC3. - Parser/muxer changes to support AC3 audio codecs. - EC3 audio sample entry will come in Part 2. - MPD signaling will come in Part 3. Issue #64 Change-Id: I790b46ae8179b933bb8f7da9cdd38591fe8da43d
This commit is contained in:
parent
b17d240060
commit
58b95fd3d5
|
@ -19,42 +19,24 @@ std::string AudioCodecToString(AudioCodec audio_codec) {
|
||||||
switch (audio_codec) {
|
switch (audio_codec) {
|
||||||
case kCodecAAC:
|
case kCodecAAC:
|
||||||
return "AAC";
|
return "AAC";
|
||||||
case kCodecMP3:
|
case kCodecAC3:
|
||||||
return "MP3";
|
return "AC3";
|
||||||
case kCodecPCM:
|
|
||||||
return "PCM";
|
|
||||||
case kCodecVorbis:
|
|
||||||
return "Vorbis";
|
|
||||||
case kCodecFLAC:
|
|
||||||
return "FLAC";
|
|
||||||
case kCodecAMR_NB:
|
|
||||||
return "AMR_NB";
|
|
||||||
case kCodecAMR_WB:
|
|
||||||
return "AMR_WB";
|
|
||||||
case kCodecPCM_MULAW:
|
|
||||||
return "PCM_MULAW";
|
|
||||||
case kCodecGSM_MS:
|
|
||||||
return "GSM_MS";
|
|
||||||
case kCodecPCM_S16BE:
|
|
||||||
return "PCM_S16BE";
|
|
||||||
case kCodecPCM_S24BE:
|
|
||||||
return "PCM_S24BE";
|
|
||||||
case kCodecOpus:
|
|
||||||
return "Opus";
|
|
||||||
case kCodecEAC3:
|
|
||||||
return "EAC3";
|
|
||||||
case kCodecDTSC:
|
case kCodecDTSC:
|
||||||
return "DTSC";
|
return "DTSC";
|
||||||
|
case kCodecDTSE:
|
||||||
|
return "DTSE";
|
||||||
case kCodecDTSH:
|
case kCodecDTSH:
|
||||||
return "DTSH";
|
return "DTSH";
|
||||||
case kCodecDTSL:
|
case kCodecDTSL:
|
||||||
return "DTSL";
|
return "DTSL";
|
||||||
case kCodecDTSE:
|
|
||||||
return "DTSE";
|
|
||||||
case kCodecDTSP:
|
|
||||||
return "DTS+";
|
|
||||||
case kCodecDTSM:
|
case kCodecDTSM:
|
||||||
return "DTS-";
|
return "DTS-";
|
||||||
|
case kCodecDTSP:
|
||||||
|
return "DTS+";
|
||||||
|
case kCodecOpus:
|
||||||
|
return "Opus";
|
||||||
|
case kCodecVorbis:
|
||||||
|
return "Vorbis";
|
||||||
default:
|
default:
|
||||||
NOTIMPLEMENTED() << "Unknown Audio Codec: " << audio_codec;
|
NOTIMPLEMENTED() << "Unknown Audio Codec: " << audio_codec;
|
||||||
return "UnknownAudioCodec";
|
return "UnknownAudioCodec";
|
||||||
|
@ -131,6 +113,8 @@ std::string AudioStreamInfo::GetCodecString(AudioCodec codec,
|
||||||
return "dts+";
|
return "dts+";
|
||||||
case kCodecDTSM:
|
case kCodecDTSM:
|
||||||
return "dts-";
|
return "dts-";
|
||||||
|
case kCodecAC3:
|
||||||
|
return "ac-3";
|
||||||
default:
|
default:
|
||||||
NOTIMPLEMENTED() << "Codec: " << codec;
|
NOTIMPLEMENTED() << "Codec: " << codec;
|
||||||
return "unknown";
|
return "unknown";
|
||||||
|
|
|
@ -17,24 +17,15 @@ namespace media {
|
||||||
enum AudioCodec {
|
enum AudioCodec {
|
||||||
kUnknownAudioCodec = 0,
|
kUnknownAudioCodec = 0,
|
||||||
kCodecAAC,
|
kCodecAAC,
|
||||||
kCodecMP3,
|
kCodecAC3,
|
||||||
kCodecPCM,
|
|
||||||
kCodecVorbis,
|
|
||||||
kCodecFLAC,
|
|
||||||
kCodecAMR_NB,
|
|
||||||
kCodecAMR_WB,
|
|
||||||
kCodecPCM_MULAW,
|
|
||||||
kCodecGSM_MS,
|
|
||||||
kCodecPCM_S16BE,
|
|
||||||
kCodecPCM_S24BE,
|
|
||||||
kCodecOpus,
|
|
||||||
kCodecEAC3,
|
|
||||||
kCodecDTSC,
|
kCodecDTSC,
|
||||||
|
kCodecDTSE,
|
||||||
kCodecDTSH,
|
kCodecDTSH,
|
||||||
kCodecDTSL,
|
kCodecDTSL,
|
||||||
kCodecDTSE,
|
|
||||||
kCodecDTSP,
|
|
||||||
kCodecDTSM,
|
kCodecDTSM,
|
||||||
|
kCodecDTSP,
|
||||||
|
kCodecOpus,
|
||||||
|
kCodecVorbis,
|
||||||
|
|
||||||
kNumAudioCodec
|
kNumAudioCodec
|
||||||
};
|
};
|
||||||
|
|
|
@ -134,7 +134,8 @@ bool FileType::ReadWriteInternal(BoxBuffer* buffer) {
|
||||||
buffer->ReadWriteUInt32(&minor_version));
|
buffer->ReadWriteUInt32(&minor_version));
|
||||||
size_t num_brands;
|
size_t num_brands;
|
||||||
if (buffer->Reading()) {
|
if (buffer->Reading()) {
|
||||||
num_brands = (buffer->Size() - buffer->Pos()) / sizeof(FourCC);
|
RCHECK(buffer->BytesLeft() % sizeof(FourCC) == 0);
|
||||||
|
num_brands = buffer->BytesLeft() / sizeof(FourCC);
|
||||||
compatible_brands.resize(num_brands);
|
compatible_brands.resize(num_brands);
|
||||||
} else {
|
} else {
|
||||||
num_brands = compatible_brands.size();
|
num_brands = compatible_brands.size();
|
||||||
|
@ -318,8 +319,8 @@ bool SampleEncryption::ReadWriteInternal(BoxBuffer* buffer) {
|
||||||
// If we don't know |iv_size|, store sample encryption data to parse later
|
// If we don't know |iv_size|, store sample encryption data to parse later
|
||||||
// after we know iv_size.
|
// after we know iv_size.
|
||||||
if (buffer->Reading() && iv_size == 0) {
|
if (buffer->Reading() && iv_size == 0) {
|
||||||
RCHECK(buffer->ReadWriteVector(&sample_encryption_data,
|
RCHECK(
|
||||||
buffer->Size() - buffer->Pos()));
|
buffer->ReadWriteVector(&sample_encryption_data, buffer->BytesLeft()));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1395,7 +1396,7 @@ bool DTSSpecific::ReadWriteInternal(BoxBuffer* buffer) {
|
||||||
buffer->ReadWriteUInt8(&pcm_sample_depth));
|
buffer->ReadWriteUInt8(&pcm_sample_depth));
|
||||||
|
|
||||||
if (buffer->Reading()) {
|
if (buffer->Reading()) {
|
||||||
RCHECK(buffer->ReadWriteVector(&extra_data, buffer->Size() - buffer->Pos()));
|
RCHECK(buffer->ReadWriteVector(&extra_data, buffer->BytesLeft()));
|
||||||
} else {
|
} else {
|
||||||
if (extra_data.empty()) {
|
if (extra_data.empty()) {
|
||||||
extra_data.assign(kDdtsExtraData,
|
extra_data.assign(kDdtsExtraData,
|
||||||
|
@ -1415,6 +1416,25 @@ uint32_t DTSSpecific::ComputeSizeInternal() {
|
||||||
sizeof(kDdtsExtraData);
|
sizeof(kDdtsExtraData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AC3Specific::AC3Specific() {}
|
||||||
|
AC3Specific::~AC3Specific() {}
|
||||||
|
|
||||||
|
FourCC AC3Specific::BoxType() const { return FOURCC_DAC3; }
|
||||||
|
|
||||||
|
bool AC3Specific::ReadWriteInternal(BoxBuffer* buffer) {
|
||||||
|
RCHECK(ReadWriteHeaderInternal(buffer) &&
|
||||||
|
buffer->ReadWriteVector(
|
||||||
|
&data, buffer->Reading() ? buffer->BytesLeft() : data.size()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t AC3Specific::ComputeSizeInternal() {
|
||||||
|
// This box is optional. Skip it if not initialized.
|
||||||
|
if (data.empty())
|
||||||
|
return 0;
|
||||||
|
return HeaderSize() + data.size();
|
||||||
|
}
|
||||||
|
|
||||||
AudioSampleEntry::AudioSampleEntry()
|
AudioSampleEntry::AudioSampleEntry()
|
||||||
: format(FOURCC_NULL),
|
: format(FOURCC_NULL),
|
||||||
data_reference_index(1),
|
data_reference_index(1),
|
||||||
|
@ -1468,15 +1488,16 @@ bool AudioSampleEntry::ReadWriteInternal(BoxBuffer* buffer) {
|
||||||
|
|
||||||
RCHECK(buffer->TryReadWriteChild(&esds));
|
RCHECK(buffer->TryReadWriteChild(&esds));
|
||||||
RCHECK(buffer->TryReadWriteChild(&ddts));
|
RCHECK(buffer->TryReadWriteChild(&ddts));
|
||||||
|
RCHECK(buffer->TryReadWriteChild(&dac3));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t AudioSampleEntry::ComputeSizeInternal() {
|
uint32_t AudioSampleEntry::ComputeSizeInternal() {
|
||||||
return HeaderSize() + sizeof(data_reference_index) + sizeof(channelcount) +
|
return HeaderSize() + sizeof(data_reference_index) + sizeof(channelcount) +
|
||||||
sizeof(samplesize) + sizeof(samplerate) + sinf.ComputeSize() +
|
sizeof(samplesize) + sizeof(samplerate) + sinf.ComputeSize() +
|
||||||
esds.ComputeSize() + ddts.ComputeSize() + 6 +
|
esds.ComputeSize() + ddts.ComputeSize() + dac3.ComputeSize() +
|
||||||
8 + // 6 + 8 bytes reserved.
|
6 + 8 + // 6 + 8 bytes reserved.
|
||||||
4; // 4 bytes predefined.
|
4; // 4 bytes predefined.
|
||||||
}
|
}
|
||||||
|
|
||||||
WebVTTConfigurationBox::WebVTTConfigurationBox() {}
|
WebVTTConfigurationBox::WebVTTConfigurationBox() {}
|
||||||
|
@ -1623,7 +1644,7 @@ FourCC DataEntryUrl::BoxType() const { return FOURCC_URL; }
|
||||||
bool DataEntryUrl::ReadWriteInternal(BoxBuffer* buffer) {
|
bool DataEntryUrl::ReadWriteInternal(BoxBuffer* buffer) {
|
||||||
RCHECK(ReadWriteHeaderInternal(buffer));
|
RCHECK(ReadWriteHeaderInternal(buffer));
|
||||||
if (buffer->Reading()) {
|
if (buffer->Reading()) {
|
||||||
RCHECK(buffer->ReadWriteVector(&location, buffer->Size() - buffer->Pos()));
|
RCHECK(buffer->ReadWriteVector(&location, buffer->BytesLeft()));
|
||||||
} else {
|
} else {
|
||||||
RCHECK(buffer->ReadWriteVector(&location, location.size()));
|
RCHECK(buffer->ReadWriteVector(&location, location.size()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -309,6 +309,12 @@ struct DTSSpecific : Box {
|
||||||
std::vector<uint8_t> extra_data;
|
std::vector<uint8_t> extra_data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct AC3Specific : Box {
|
||||||
|
DECLARE_BOX_METHODS(AC3Specific);
|
||||||
|
|
||||||
|
std::vector<uint8_t> data;
|
||||||
|
};
|
||||||
|
|
||||||
struct AudioSampleEntry : Box {
|
struct AudioSampleEntry : Box {
|
||||||
DECLARE_BOX_METHODS(AudioSampleEntry);
|
DECLARE_BOX_METHODS(AudioSampleEntry);
|
||||||
// Returns actual format of this sample entry.
|
// Returns actual format of this sample entry.
|
||||||
|
@ -325,6 +331,7 @@ struct AudioSampleEntry : Box {
|
||||||
ProtectionSchemeInfo sinf;
|
ProtectionSchemeInfo sinf;
|
||||||
ElementaryStreamDescriptor esds;
|
ElementaryStreamDescriptor esds;
|
||||||
DTSSpecific ddts;
|
DTSSpecific ddts;
|
||||||
|
AC3Specific dac3;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct WebVTTConfigurationBox : Box {
|
struct WebVTTConfigurationBox : Box {
|
||||||
|
|
|
@ -240,14 +240,19 @@ inline bool operator==(const DTSSpecific& lhs,
|
||||||
lhs.extra_data == rhs.extra_data;
|
lhs.extra_data == rhs.extra_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool operator==(const AC3Specific& lhs,
|
||||||
|
const AC3Specific& rhs) {
|
||||||
|
return lhs.data == rhs.data;
|
||||||
|
}
|
||||||
|
|
||||||
inline bool operator==(const AudioSampleEntry& lhs,
|
inline bool operator==(const AudioSampleEntry& lhs,
|
||||||
const AudioSampleEntry& rhs) {
|
const AudioSampleEntry& rhs) {
|
||||||
return lhs.format == rhs.format &&
|
return lhs.format == rhs.format &&
|
||||||
lhs.data_reference_index == rhs.data_reference_index &&
|
lhs.data_reference_index == rhs.data_reference_index &&
|
||||||
lhs.channelcount == rhs.channelcount &&
|
lhs.channelcount == rhs.channelcount &&
|
||||||
lhs.samplesize == rhs.samplesize && lhs.samplerate == rhs.samplerate &&
|
lhs.samplesize == rhs.samplesize && lhs.samplerate == rhs.samplerate &&
|
||||||
lhs.sinf == rhs.sinf && lhs.esds == rhs.esds &&
|
lhs.sinf == rhs.sinf && lhs.esds == rhs.esds && lhs.ddts == rhs.ddts &&
|
||||||
lhs.ddts == rhs.ddts;
|
lhs.dac3 == rhs.dac3;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool operator==(const WebVTTConfigurationBox& lhs,
|
inline bool operator==(const WebVTTConfigurationBox& lhs,
|
||||||
|
|
|
@ -380,6 +380,16 @@ class BoxDefinitionsTestGeneral : public testing::Test {
|
||||||
ddts->pcm_sample_depth = 24;
|
ddts->pcm_sample_depth = 24;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Fill(AC3Specific* dac3) {
|
||||||
|
const uint8_t kAc3Data[] = {0x50, 0x11, 0x60};
|
||||||
|
dac3->data.assign(kAc3Data, kAc3Data + arraysize(kAc3Data));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Modify(AC3Specific* dac3) {
|
||||||
|
const uint8_t kAc3Data[] = {0x50, 0x11, 0x40};
|
||||||
|
dac3->data.assign(kAc3Data, kAc3Data + arraysize(kAc3Data));
|
||||||
|
}
|
||||||
|
|
||||||
void Fill(AudioSampleEntry* enca) {
|
void Fill(AudioSampleEntry* enca) {
|
||||||
enca->format = FOURCC_ENCA;
|
enca->format = FOURCC_ENCA;
|
||||||
enca->data_reference_index = 2;
|
enca->data_reference_index = 2;
|
||||||
|
@ -884,6 +894,7 @@ class BoxDefinitionsTestGeneral : public testing::Test {
|
||||||
bool IsOptional(const CodecConfigurationRecord* box) { return true; }
|
bool IsOptional(const CodecConfigurationRecord* box) { return true; }
|
||||||
bool IsOptional(const PixelAspectRatio* box) { return true; }
|
bool IsOptional(const PixelAspectRatio* box) { return true; }
|
||||||
bool IsOptional(const ElementaryStreamDescriptor* box) { return true; }
|
bool IsOptional(const ElementaryStreamDescriptor* box) { return true; }
|
||||||
|
bool IsOptional(const AC3Specific* box) { return true; }
|
||||||
// Recommended, but optional.
|
// Recommended, but optional.
|
||||||
bool IsOptional(const WebVTTSourceLabelBox* box) { return true; }
|
bool IsOptional(const WebVTTSourceLabelBox* box) { return true; }
|
||||||
bool IsOptional(const CompositionTimeToSample* box) { return true; }
|
bool IsOptional(const CompositionTimeToSample* box) { return true; }
|
||||||
|
@ -926,6 +937,7 @@ typedef testing::Types<FileType,
|
||||||
VideoSampleEntry,
|
VideoSampleEntry,
|
||||||
ElementaryStreamDescriptor,
|
ElementaryStreamDescriptor,
|
||||||
DTSSpecific,
|
DTSSpecific,
|
||||||
|
AC3Specific,
|
||||||
AudioSampleEntry,
|
AudioSampleEntry,
|
||||||
WebVTTConfigurationBox,
|
WebVTTConfigurationBox,
|
||||||
WebVTTSourceLabelBox,
|
WebVTTSourceLabelBox,
|
||||||
|
@ -1072,6 +1084,21 @@ TEST_F(BoxDefinitionsTest, DTSSampleEntry) {
|
||||||
ASSERT_EQ(entry, entry_readback);
|
ASSERT_EQ(entry, entry_readback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(BoxDefinitionsTest, AC3SampleEntry) {
|
||||||
|
AudioSampleEntry entry;
|
||||||
|
entry.format = FOURCC_AC3;
|
||||||
|
entry.data_reference_index = 2;
|
||||||
|
entry.channelcount = 5;
|
||||||
|
entry.samplesize = 16;
|
||||||
|
entry.samplerate = 44100;
|
||||||
|
Fill(&entry.dac3);
|
||||||
|
entry.Write(this->buffer_.get());
|
||||||
|
|
||||||
|
AudioSampleEntry entry_readback;
|
||||||
|
ASSERT_TRUE(ReadBack(&entry_readback));
|
||||||
|
ASSERT_EQ(entry, entry_readback);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(BoxDefinitionsTest, ProtectionSystemSpecificHeader) {
|
TEST_F(BoxDefinitionsTest, ProtectionSystemSpecificHeader) {
|
||||||
ProtectionSystemSpecificHeader pssh;
|
ProtectionSystemSpecificHeader pssh;
|
||||||
Fill(&pssh);
|
Fill(&pssh);
|
||||||
|
|
|
@ -16,12 +16,14 @@ enum FourCC {
|
||||||
FOURCC_NULL = 0,
|
FOURCC_NULL = 0,
|
||||||
FOURCC_ID32 = 0x49443332,
|
FOURCC_ID32 = 0x49443332,
|
||||||
FOURCC_PRIV = 0x50524956,
|
FOURCC_PRIV = 0x50524956,
|
||||||
|
FOURCC_AC3 = 0x61632d33, // This fourcc is "ac-3".
|
||||||
FOURCC_AVC1 = 0x61766331,
|
FOURCC_AVC1 = 0x61766331,
|
||||||
FOURCC_AVCC = 0x61766343,
|
FOURCC_AVCC = 0x61766343,
|
||||||
FOURCC_BLOC = 0x626C6F63,
|
FOURCC_BLOC = 0x626C6F63,
|
||||||
FOURCC_CENC = 0x63656e63,
|
FOURCC_CENC = 0x63656e63,
|
||||||
FOURCC_CO64 = 0x636f3634,
|
FOURCC_CO64 = 0x636f3634,
|
||||||
FOURCC_CTTS = 0x63747473,
|
FOURCC_CTTS = 0x63747473,
|
||||||
|
FOURCC_DAC3 = 0x64616333,
|
||||||
FOURCC_DASH = 0x64617368,
|
FOURCC_DASH = 0x64617368,
|
||||||
FOURCC_DDTS = 0x64647473,
|
FOURCC_DDTS = 0x64647473,
|
||||||
FOURCC_DINF = 0x64696e66,
|
FOURCC_DINF = 0x64696e66,
|
||||||
|
@ -32,7 +34,6 @@ enum FourCC {
|
||||||
FOURCC_DTSL = 0x6474736c,
|
FOURCC_DTSL = 0x6474736c,
|
||||||
FOURCC_DTSM = 0x6474732d,
|
FOURCC_DTSM = 0x6474732d,
|
||||||
FOURCC_DTSP = 0x6474732b,
|
FOURCC_DTSP = 0x6474732b,
|
||||||
FOURCC_EAC3 = 0x65632d33,
|
|
||||||
FOURCC_EDTS = 0x65647473,
|
FOURCC_EDTS = 0x65647473,
|
||||||
FOURCC_ELST = 0x656c7374,
|
FOURCC_ELST = 0x656c7374,
|
||||||
FOURCC_ENCA = 0x656e6361,
|
FOURCC_ENCA = 0x656e6361,
|
||||||
|
|
|
@ -74,8 +74,8 @@ AudioCodec FourCCToAudioCodec(FourCC fourcc) {
|
||||||
return kCodecDTSP;
|
return kCodecDTSP;
|
||||||
case FOURCC_DTSM:
|
case FOURCC_DTSM:
|
||||||
return kCodecDTSM;
|
return kCodecDTSM;
|
||||||
case FOURCC_EAC3:
|
case FOURCC_AC3:
|
||||||
return kCodecEAC3;
|
return kCodecAC3;
|
||||||
default:
|
default:
|
||||||
return kUnknownAudioCodec;
|
return kUnknownAudioCodec;
|
||||||
}
|
}
|
||||||
|
@ -410,7 +410,8 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) {
|
||||||
num_channels = entry.channelcount;
|
num_channels = entry.channelcount;
|
||||||
sampling_frequency = entry.samplerate;
|
sampling_frequency = entry.samplerate;
|
||||||
break;
|
break;
|
||||||
case FOURCC_EAC3:
|
case FOURCC_AC3:
|
||||||
|
extra_data = entry.dac3.data;
|
||||||
num_channels = entry.channelcount;
|
num_channels = entry.channelcount;
|
||||||
sampling_frequency = entry.samplerate;
|
sampling_frequency = entry.samplerate;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -63,6 +63,8 @@ FourCC AudioCodecToFourCC(AudioCodec codec) {
|
||||||
switch (codec) {
|
switch (codec) {
|
||||||
case kCodecAAC:
|
case kCodecAAC:
|
||||||
return FOURCC_MP4A;
|
return FOURCC_MP4A;
|
||||||
|
case kCodecAC3:
|
||||||
|
return FOURCC_AC3;
|
||||||
case kCodecDTSC:
|
case kCodecDTSC:
|
||||||
return FOURCC_DTSC;
|
return FOURCC_DTSC;
|
||||||
case kCodecDTSH:
|
case kCodecDTSH:
|
||||||
|
@ -261,6 +263,9 @@ void MP4Muxer::GenerateAudioTrak(const AudioStreamInfo* audio_info,
|
||||||
audio.ddts.sampling_frequency = audio_info->sampling_frequency();
|
audio.ddts.sampling_frequency = audio_info->sampling_frequency();
|
||||||
audio.ddts.pcm_sample_depth = audio_info->sample_bits();
|
audio.ddts.pcm_sample_depth = audio_info->sample_bits();
|
||||||
break;
|
break;
|
||||||
|
case kCodecAC3:
|
||||||
|
audio.dac3.data = audio_info->extra_data();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
NOTIMPLEMENTED();
|
NOTIMPLEMENTED();
|
||||||
break;
|
break;
|
||||||
|
|
Loading…
Reference in New Issue