зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1022501 - Add MP3 support to MP4 demuxer. r=edwin
--HG-- rename : content/media/fmp4/eme/EMEAACDecoder.cpp => content/media/fmp4/eme/EMEAudioDecoder.cpp rename : content/media/fmp4/eme/EMEAACDecoder.h => content/media/fmp4/eme/EMEAudioDecoder.h rename : content/media/fmp4/ffmpeg/FFmpegAACDecoder.cpp => content/media/fmp4/ffmpeg/FFmpegAudioDecoder.cpp rename : content/media/fmp4/ffmpeg/FFmpegAACDecoder.h => content/media/fmp4/ffmpeg/FFmpegAudioDecoder.h
This commit is contained in:
Родитель
4bd9fb18d1
Коммит
42cab9e349
|
@ -229,9 +229,9 @@ public:
|
|||
|
||||
// Decode thread.
|
||||
virtual already_AddRefed<MediaDataDecoder>
|
||||
CreateAACDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
|
||||
MediaTaskQueue* aAudioTaskQueue,
|
||||
MediaDataDecoderCallback* aCallback) MOZ_OVERRIDE {
|
||||
CreateAudioDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
|
||||
MediaTaskQueue* aAudioTaskQueue,
|
||||
MediaDataDecoderCallback* aCallback) MOZ_OVERRIDE {
|
||||
BlankAudioDataCreator* creator = new BlankAudioDataCreator(
|
||||
aConfig.channel_count, aConfig.samples_per_second);
|
||||
|
||||
|
@ -241,6 +241,13 @@ public:
|
|||
aCallback);
|
||||
return decoder.forget();
|
||||
}
|
||||
|
||||
virtual bool
|
||||
SupportsAudioMimeType(const char* aMimeType) MOZ_OVERRIDE
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
PlatformDecoderModule* CreateBlankDecoderModule()
|
||||
|
|
|
@ -288,6 +288,14 @@ MP4Reader::ExtractCryptoInitData(nsTArray<uint8_t>& aInitData)
|
|||
}
|
||||
}
|
||||
|
||||
bool
|
||||
MP4Reader::IsSupportedAudioMimeType(const char* aMimeType)
|
||||
{
|
||||
return (!strcmp(aMimeType, "audio/mpeg") ||
|
||||
!strcmp(aMimeType, "audio/mp4a-latm")) &&
|
||||
mPlatform->SupportsAudioMimeType(aMimeType);
|
||||
}
|
||||
|
||||
nsresult
|
||||
MP4Reader::ReadMetadata(MediaInfo* aInfo,
|
||||
MetadataTags** aTags)
|
||||
|
@ -296,13 +304,6 @@ MP4Reader::ReadMetadata(MediaInfo* aInfo,
|
|||
bool ok = mDemuxer->Init();
|
||||
NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
|
||||
|
||||
mInfo.mAudio.mHasAudio = mAudio.mActive = mDemuxer->HasValidAudio();
|
||||
const AudioDecoderConfig& audio = mDemuxer->AudioConfig();
|
||||
// If we have audio, we *only* allow AAC to be decoded.
|
||||
if (mInfo.mAudio.mHasAudio && strcmp(audio.mime_type, "audio/mp4a-latm")) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
mInfo.mVideo.mHasVideo = mVideo.mActive = mDemuxer->HasValidVideo();
|
||||
const VideoDecoderConfig& video = mDemuxer->VideoConfig();
|
||||
// If we have video, we *only* allow H.264 to be decoded.
|
||||
|
@ -370,14 +371,18 @@ MP4Reader::ReadMetadata(MediaInfo* aInfo,
|
|||
NS_ENSURE_TRUE(mPlatform, NS_ERROR_FAILURE);
|
||||
}
|
||||
|
||||
if (HasAudio()) {
|
||||
if (mDemuxer->HasValidAudio()) {
|
||||
const AudioDecoderConfig& audio = mDemuxer->AudioConfig();
|
||||
mInfo.mAudio.mHasAudio = mAudio.mActive = true;
|
||||
if (mInfo.mAudio.mHasAudio && !IsSupportedAudioMimeType(audio.mime_type)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
mInfo.mAudio.mRate = audio.samples_per_second;
|
||||
mInfo.mAudio.mChannels = audio.channel_count;
|
||||
mAudio.mCallback = new DecoderCallback(this, kAudio);
|
||||
mAudio.mDecoder = mPlatform->CreateAACDecoder(audio,
|
||||
mAudio.mTaskQueue,
|
||||
mAudio.mCallback);
|
||||
mAudio.mDecoder = mPlatform->CreateAudioDecoder(audio,
|
||||
mAudio.mTaskQueue,
|
||||
mAudio.mCallback);
|
||||
NS_ENSURE_TRUE(mAudio.mDecoder != nullptr, NS_ERROR_FAILURE);
|
||||
nsresult rv = mAudio.mDecoder->Init();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
|
|
@ -87,6 +87,7 @@ private:
|
|||
void Flush(mp4_demuxer::TrackType aTrack);
|
||||
void DrainComplete(mp4_demuxer::TrackType aTrack);
|
||||
void UpdateIndex();
|
||||
bool IsSupportedAudioMimeType(const char* aMimeType);
|
||||
void NotifyResourcesStatusChanged();
|
||||
bool IsWaitingOnCodecResource();
|
||||
bool IsWaitingOnCDMResource();
|
||||
|
|
|
@ -153,4 +153,10 @@ PlatformDecoderModule::Create()
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
bool
|
||||
PlatformDecoderModule::SupportsAudioMimeType(const char* aMimeType)
|
||||
{
|
||||
return !strcmp(aMimeType, "audio/mp4a-latm");
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -35,8 +35,8 @@ class CDMProxy;
|
|||
typedef int64_t Microseconds;
|
||||
|
||||
// The PlatformDecoderModule interface is used by the MP4Reader to abstract
|
||||
// access to the H264 and AAC decoders provided by various platforms. It
|
||||
// may be extended to support other codecs in future. Each platform (Windows,
|
||||
// access to the H264 and Audio (AAC/MP3) decoders provided by various platforms.
|
||||
// It may be extended to support other codecs in future. Each platform (Windows,
|
||||
// MacOSX, Linux, B2G etc) must implement a PlatformDecoderModule to provide
|
||||
// access to its decoders in order to get decompressed H.264/AAC from the
|
||||
// MP4Reader.
|
||||
|
@ -103,7 +103,7 @@ public:
|
|||
MediaTaskQueue* aVideoTaskQueue,
|
||||
MediaDataDecoderCallback* aCallback) = 0;
|
||||
|
||||
// Creates an AAC decoder with the specified properties.
|
||||
// Creates an Audio decoder with the specified properties.
|
||||
// Asynchronous decoding of audio should be done in runnables dispatched to
|
||||
// aAudioTaskQueue. If the task queue isn't needed, the decoder should
|
||||
// not hold a reference to it.
|
||||
|
@ -114,9 +114,14 @@ public:
|
|||
// It is safe to store a reference to aConfig.
|
||||
// This is called on the decode task queue.
|
||||
virtual already_AddRefed<MediaDataDecoder>
|
||||
CreateAACDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
|
||||
MediaTaskQueue* aAudioTaskQueue,
|
||||
MediaDataDecoderCallback* aCallback) = 0;
|
||||
CreateAudioDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
|
||||
MediaTaskQueue* aAudioTaskQueue,
|
||||
MediaDataDecoderCallback* aCallback) = 0;
|
||||
|
||||
// An audio decoder module must support AAC by default.
|
||||
// If more audio codec is to be supported, SupportsAudioMimeType will have
|
||||
// to be extended
|
||||
virtual bool SupportsAudioMimeType(const char* aMimeType);
|
||||
|
||||
virtual ~PlatformDecoderModule() {}
|
||||
|
||||
|
|
|
@ -37,13 +37,20 @@ AppleATDecoder::AppleATDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
|
|||
, mHaveOutput(false)
|
||||
{
|
||||
MOZ_COUNT_CTOR(AppleATDecoder);
|
||||
LOG("Creating Apple AudioToolbox AAC decoder");
|
||||
LOG("Creating Apple AudioToolbox Audio decoder");
|
||||
LOG("Audio Decoder configuration: %s %d Hz %d channels %d bits per channel",
|
||||
mConfig.mime_type,
|
||||
mConfig.samples_per_second,
|
||||
mConfig.channel_count,
|
||||
mConfig.bits_per_sample);
|
||||
// TODO: Verify aConfig.mime_type.
|
||||
|
||||
if (!strcmp(aConfig.mime_type, "audio/mpeg")) {
|
||||
mFileType = kAudioFileMP3Type;
|
||||
} else if (!strcmp(aConfig.mime_type, "audio/mp4a-latm")) {
|
||||
mFileType = kAudioFileAAC_ADTSType;
|
||||
} else {
|
||||
mFileType = 0;
|
||||
}
|
||||
}
|
||||
|
||||
AppleATDecoder::~AppleATDecoder()
|
||||
|
@ -79,12 +86,15 @@ _SampleCallback(void *aDecoder,
|
|||
nsresult
|
||||
AppleATDecoder::Init()
|
||||
{
|
||||
LOG("Initializing Apple AudioToolbox AAC decoder");
|
||||
AudioFileTypeID fileType = kAudioFileAAC_ADTSType;
|
||||
if (!mFileType) {
|
||||
NS_ERROR("Non recognised format");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
LOG("Initializing Apple AudioToolbox Audio decoder");
|
||||
OSStatus rv = AudioFileStreamOpen(this,
|
||||
_MetadataCallback,
|
||||
_SampleCallback,
|
||||
fileType,
|
||||
mFileType,
|
||||
&mStream);
|
||||
if (rv) {
|
||||
NS_ERROR("Couldn't open AudioFileStream");
|
||||
|
|
|
@ -53,6 +53,7 @@ private:
|
|||
int64_t mSamplePosition;
|
||||
bool mHaveOutput;
|
||||
AudioStreamBasicDescription mOutputFormat;
|
||||
AudioFileTypeID mFileType;
|
||||
|
||||
void SetupDecoder();
|
||||
void SubmitSample(nsAutoPtr<mp4_demuxer::MP4Sample> aSample);
|
||||
|
|
|
@ -87,13 +87,19 @@ AppleDecoderModule::CreateH264Decoder(const mp4_demuxer::VideoDecoderConfig& aCo
|
|||
}
|
||||
|
||||
already_AddRefed<MediaDataDecoder>
|
||||
AppleDecoderModule::CreateAACDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
|
||||
MediaTaskQueue* aAudioTaskQueue,
|
||||
MediaDataDecoderCallback* aCallback)
|
||||
AppleDecoderModule::CreateAudioDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
|
||||
MediaTaskQueue* aAudioTaskQueue,
|
||||
MediaDataDecoderCallback* aCallback)
|
||||
{
|
||||
nsRefPtr<MediaDataDecoder> decoder =
|
||||
new AppleATDecoder(aConfig, aAudioTaskQueue, aCallback);
|
||||
return decoder.forget();
|
||||
}
|
||||
|
||||
bool
|
||||
AppleDecoderModule::SupportsAudioMimeType(const char* aMimeType)
|
||||
{
|
||||
return !strcmp(aMimeType, "audio/mp4a-latm") || !strcmp(aMimeType, "audio/mpeg");
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -34,9 +34,11 @@ public:
|
|||
|
||||
// Decode thread.
|
||||
virtual already_AddRefed<MediaDataDecoder>
|
||||
CreateAACDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
|
||||
MediaTaskQueue* aAudioTaskQueue,
|
||||
MediaDataDecoderCallback* aCallback) MOZ_OVERRIDE;
|
||||
CreateAudioDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
|
||||
MediaTaskQueue* aAudioTaskQueue,
|
||||
MediaDataDecoderCallback* aCallback) MOZ_OVERRIDE;
|
||||
|
||||
virtual bool SupportsAudioMimeType(const char* aMimeType) MOZ_OVERRIDE;
|
||||
|
||||
static void Init();
|
||||
private:
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "EMEAACDecoder.h"
|
||||
#include "EMEAudioDecoder.h"
|
||||
#include "mp4_demuxer/DecoderData.h"
|
||||
#include "mozilla/EMELog.h"
|
||||
#include "gmp-audio-host.h"
|
||||
|
@ -18,10 +18,10 @@
|
|||
|
||||
namespace mozilla {
|
||||
|
||||
EMEAACDecoder::EMEAACDecoder(CDMProxy* aProxy,
|
||||
const AudioDecoderConfig& aConfig,
|
||||
MediaTaskQueue* aTaskQueue,
|
||||
MediaDataDecoderCallback* aCallback)
|
||||
EMEAudioDecoder::EMEAudioDecoder(CDMProxy* aProxy,
|
||||
const AudioDecoderConfig& aConfig,
|
||||
MediaTaskQueue* aTaskQueue,
|
||||
MediaDataDecoderCallback* aCallback)
|
||||
: mAudioRate(0)
|
||||
, mAudioBytesPerSample(0)
|
||||
, mAudioChannels(0)
|
||||
|
@ -34,17 +34,17 @@ EMEAACDecoder::EMEAACDecoder(CDMProxy* aProxy,
|
|||
, mConfig(aConfig)
|
||||
, mTaskQueue(aTaskQueue)
|
||||
, mCallback(aCallback)
|
||||
, mMonitor("EMEAACDecoder")
|
||||
, mMonitor("EMEAudioDecoder")
|
||||
, mFlushComplete(false)
|
||||
{
|
||||
}
|
||||
|
||||
EMEAACDecoder::~EMEAACDecoder()
|
||||
EMEAudioDecoder::~EMEAudioDecoder()
|
||||
{
|
||||
}
|
||||
|
||||
nsresult
|
||||
EMEAACDecoder::Init()
|
||||
EMEAudioDecoder::Init()
|
||||
{
|
||||
// Note: this runs on the decode task queue.
|
||||
|
||||
|
@ -65,7 +65,7 @@ EMEAACDecoder::Init()
|
|||
}
|
||||
|
||||
nsresult
|
||||
EMEAACDecoder::Input(MP4Sample* aSample)
|
||||
EMEAudioDecoder::Input(MP4Sample* aSample)
|
||||
{
|
||||
MOZ_ASSERT(!IsOnGMPThread()); // Runs on the decode task queue.
|
||||
|
||||
|
@ -77,7 +77,7 @@ EMEAACDecoder::Input(MP4Sample* aSample)
|
|||
}
|
||||
|
||||
nsresult
|
||||
EMEAACDecoder::Flush()
|
||||
EMEAudioDecoder::Flush()
|
||||
{
|
||||
MOZ_ASSERT(!IsOnGMPThread()); // Runs on the decode task queue.
|
||||
|
||||
|
@ -87,7 +87,7 @@ EMEAACDecoder::Flush()
|
|||
}
|
||||
|
||||
nsRefPtr<nsIRunnable> task;
|
||||
task = NS_NewRunnableMethod(this, &EMEAACDecoder::GmpFlush);
|
||||
task = NS_NewRunnableMethod(this, &EMEAudioDecoder::GmpFlush);
|
||||
nsresult rv = mGMPThread->Dispatch(task, NS_DISPATCH_NORMAL);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
|
@ -102,34 +102,34 @@ EMEAACDecoder::Flush()
|
|||
}
|
||||
|
||||
nsresult
|
||||
EMEAACDecoder::Drain()
|
||||
EMEAudioDecoder::Drain()
|
||||
{
|
||||
MOZ_ASSERT(!IsOnGMPThread()); // Runs on the decode task queue.
|
||||
|
||||
nsRefPtr<nsIRunnable> task;
|
||||
task = NS_NewRunnableMethod(this, &EMEAACDecoder::GmpDrain);
|
||||
task = NS_NewRunnableMethod(this, &EMEAudioDecoder::GmpDrain);
|
||||
nsresult rv = mGMPThread->Dispatch(task, NS_DISPATCH_NORMAL);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
EMEAACDecoder::Shutdown()
|
||||
EMEAudioDecoder::Shutdown()
|
||||
{
|
||||
MOZ_ASSERT(!IsOnGMPThread()); // Runs on the decode task queue.
|
||||
|
||||
nsRefPtr<nsIRunnable> task;
|
||||
task = NS_NewRunnableMethod(this, &EMEAACDecoder::GmpShutdown);
|
||||
task = NS_NewRunnableMethod(this, &EMEAudioDecoder::GmpShutdown);
|
||||
nsresult rv = mGMPThread->Dispatch(task, NS_DISPATCH_SYNC);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
EMEAACDecoder::Decoded(const nsTArray<int16_t>& aPCM,
|
||||
uint64_t aTimeStamp,
|
||||
uint32_t aChannels,
|
||||
uint32_t aRate)
|
||||
EMEAudioDecoder::Decoded(const nsTArray<int16_t>& aPCM,
|
||||
uint64_t aTimeStamp,
|
||||
uint32_t aChannels,
|
||||
uint32_t aRate)
|
||||
{
|
||||
MOZ_ASSERT(IsOnGMPThread());
|
||||
|
||||
|
@ -192,21 +192,21 @@ EMEAACDecoder::Decoded(const nsTArray<int16_t>& aPCM,
|
|||
}
|
||||
|
||||
void
|
||||
EMEAACDecoder::InputDataExhausted()
|
||||
EMEAudioDecoder::InputDataExhausted()
|
||||
{
|
||||
MOZ_ASSERT(IsOnGMPThread());
|
||||
mCallback->InputExhausted();
|
||||
}
|
||||
|
||||
void
|
||||
EMEAACDecoder::DrainComplete()
|
||||
EMEAudioDecoder::DrainComplete()
|
||||
{
|
||||
MOZ_ASSERT(IsOnGMPThread());
|
||||
mCallback->DrainComplete();
|
||||
}
|
||||
|
||||
void
|
||||
EMEAACDecoder::ResetComplete()
|
||||
EMEAudioDecoder::ResetComplete()
|
||||
{
|
||||
MOZ_ASSERT(IsOnGMPThread());
|
||||
mMustRecaptureAudioPosition = true;
|
||||
|
@ -218,23 +218,23 @@ EMEAACDecoder::ResetComplete()
|
|||
}
|
||||
|
||||
void
|
||||
EMEAACDecoder::Error(GMPErr aErr)
|
||||
EMEAudioDecoder::Error(GMPErr aErr)
|
||||
{
|
||||
MOZ_ASSERT(IsOnGMPThread());
|
||||
EME_LOG("EMEAACDecoder::Error");
|
||||
EME_LOG("EMEAudioDecoder::Error");
|
||||
mCallback->Error();
|
||||
GmpShutdown();
|
||||
}
|
||||
|
||||
void
|
||||
EMEAACDecoder::Terminated()
|
||||
EMEAudioDecoder::Terminated()
|
||||
{
|
||||
MOZ_ASSERT(IsOnGMPThread());
|
||||
GmpShutdown();
|
||||
}
|
||||
|
||||
nsresult
|
||||
EMEAACDecoder::GmpInit()
|
||||
EMEAudioDecoder::GmpInit()
|
||||
{
|
||||
MOZ_ASSERT(IsOnGMPThread());
|
||||
|
||||
|
@ -266,7 +266,7 @@ EMEAACDecoder::GmpInit()
|
|||
}
|
||||
|
||||
nsresult
|
||||
EMEAACDecoder::GmpInput(MP4Sample* aSample)
|
||||
EMEAudioDecoder::GmpInput(MP4Sample* aSample)
|
||||
{
|
||||
MOZ_ASSERT(IsOnGMPThread());
|
||||
nsAutoPtr<MP4Sample> sample(aSample);
|
||||
|
@ -296,7 +296,7 @@ EMEAACDecoder::GmpInput(MP4Sample* aSample)
|
|||
}
|
||||
|
||||
void
|
||||
EMEAACDecoder::GmpFlush()
|
||||
EMEAudioDecoder::GmpFlush()
|
||||
{
|
||||
MOZ_ASSERT(IsOnGMPThread());
|
||||
if (!mGMP || NS_FAILED(mGMP->Reset())) {
|
||||
|
@ -308,7 +308,7 @@ EMEAACDecoder::GmpFlush()
|
|||
}
|
||||
|
||||
void
|
||||
EMEAACDecoder::GmpDrain()
|
||||
EMEAudioDecoder::GmpDrain()
|
||||
{
|
||||
MOZ_ASSERT(IsOnGMPThread());
|
||||
if (!mGMP || NS_FAILED(mGMP->Drain())) {
|
||||
|
@ -317,7 +317,7 @@ EMEAACDecoder::GmpDrain()
|
|||
}
|
||||
|
||||
void
|
||||
EMEAACDecoder::GmpShutdown()
|
||||
EMEAudioDecoder::GmpShutdown()
|
||||
{
|
||||
MOZ_ASSERT(IsOnGMPThread());
|
||||
if (!mGMP) {
|
|
@ -16,18 +16,18 @@
|
|||
|
||||
namespace mozilla {
|
||||
|
||||
class EMEAACDecoder : public MediaDataDecoder
|
||||
, public GMPAudioDecoderProxyCallback
|
||||
class EMEAudioDecoder : public MediaDataDecoder
|
||||
, public GMPAudioDecoderProxyCallback
|
||||
{
|
||||
typedef mp4_demuxer::MP4Sample MP4Sample;
|
||||
typedef mp4_demuxer::AudioDecoderConfig AudioDecoderConfig;
|
||||
public:
|
||||
EMEAACDecoder(CDMProxy* aProxy,
|
||||
const AudioDecoderConfig& aConfig,
|
||||
MediaTaskQueue* aTaskQueue,
|
||||
MediaDataDecoderCallback* aCallback);
|
||||
EMEAudioDecoder(CDMProxy* aProxy,
|
||||
const AudioDecoderConfig& aConfig,
|
||||
MediaTaskQueue* aTaskQueue,
|
||||
MediaDataDecoderCallback* aCallback);
|
||||
|
||||
~EMEAACDecoder();
|
||||
~EMEAudioDecoder();
|
||||
|
||||
// MediaDataDecoder implementation.
|
||||
virtual nsresult Init() MOZ_OVERRIDE;
|
||||
|
@ -51,7 +51,7 @@ private:
|
|||
|
||||
class DeliverSample : public nsRunnable {
|
||||
public:
|
||||
DeliverSample(EMEAACDecoder* aDecoder,
|
||||
DeliverSample(EMEAudioDecoder* aDecoder,
|
||||
mp4_demuxer::MP4Sample* aSample)
|
||||
: mDecoder(aDecoder)
|
||||
, mSample(aSample)
|
||||
|
@ -62,13 +62,13 @@ private:
|
|||
return NS_OK;
|
||||
}
|
||||
private:
|
||||
nsRefPtr<EMEAACDecoder> mDecoder;
|
||||
nsRefPtr<EMEAudioDecoder> mDecoder;
|
||||
nsAutoPtr<mp4_demuxer::MP4Sample> mSample;
|
||||
};
|
||||
|
||||
class InitTask : public nsRunnable {
|
||||
public:
|
||||
InitTask(EMEAACDecoder* aDecoder)
|
||||
InitTask(EMEAudioDecoder* aDecoder)
|
||||
: mDecoder(aDecoder)
|
||||
{}
|
||||
NS_IMETHOD Run() {
|
||||
|
@ -76,7 +76,7 @@ private:
|
|||
return NS_OK;
|
||||
}
|
||||
nsresult mResult;
|
||||
EMEAACDecoder* mDecoder;
|
||||
EMEAudioDecoder* mDecoder;
|
||||
};
|
||||
|
||||
nsresult GmpInit();
|
||||
|
@ -114,4 +114,4 @@ private:
|
|||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
||||
#endif
|
|
@ -19,7 +19,7 @@
|
|||
#include "SharedThreadPool.h"
|
||||
#include "mozilla/EMELog.h"
|
||||
#include "EMEH264Decoder.h"
|
||||
#include "EMEAACDecoder.h"
|
||||
#include "EMEAudioDecoder.h"
|
||||
#include <string>
|
||||
|
||||
namespace mozilla {
|
||||
|
@ -227,21 +227,21 @@ EMEDecoderModule::CreateH264Decoder(const VideoDecoderConfig& aConfig,
|
|||
}
|
||||
|
||||
already_AddRefed<MediaDataDecoder>
|
||||
EMEDecoderModule::CreateAACDecoder(const AudioDecoderConfig& aConfig,
|
||||
MediaTaskQueue* aAudioTaskQueue,
|
||||
MediaDataDecoderCallback* aCallback)
|
||||
EMEDecoderModule::CreateAudioDecoder(const AudioDecoderConfig& aConfig,
|
||||
MediaTaskQueue* aAudioTaskQueue,
|
||||
MediaDataDecoderCallback* aCallback)
|
||||
{
|
||||
if (mCDMDecodesAudio) {
|
||||
nsRefPtr<MediaDataDecoder> decoder(new EMEAACDecoder(mProxy,
|
||||
aConfig,
|
||||
aAudioTaskQueue,
|
||||
aCallback));
|
||||
nsRefPtr<MediaDataDecoder> decoder(new EMEAudioDecoder(mProxy,
|
||||
aConfig,
|
||||
aAudioTaskQueue,
|
||||
aCallback));
|
||||
return decoder.forget();
|
||||
}
|
||||
|
||||
nsRefPtr<MediaDataDecoder> decoder(mPDM->CreateAACDecoder(aConfig,
|
||||
aAudioTaskQueue,
|
||||
aCallback));
|
||||
nsRefPtr<MediaDataDecoder> decoder(mPDM->CreateAudioDecoder(aConfig,
|
||||
aAudioTaskQueue,
|
||||
aCallback));
|
||||
if (!decoder) {
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
@ -42,9 +42,9 @@ public:
|
|||
|
||||
// Decode thread.
|
||||
virtual already_AddRefed<MediaDataDecoder>
|
||||
CreateAACDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
|
||||
MediaTaskQueue* aAudioTaskQueue,
|
||||
MediaDataDecoderCallback* aCallback) MOZ_OVERRIDE;
|
||||
CreateAudioDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
|
||||
MediaTaskQueue* aAudioTaskQueue,
|
||||
MediaDataDecoderCallback* aCallback) MOZ_OVERRIDE;
|
||||
|
||||
private:
|
||||
nsRefPtr<CDMProxy> mProxy;
|
||||
|
|
|
@ -5,13 +5,13 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
EXPORTS += [
|
||||
'EMEAACDecoder.h',
|
||||
'EMEAudioDecoder.h',
|
||||
'EMEDecoderModule.h',
|
||||
'EMEH264Decoder.h',
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'EMEAACDecoder.cpp',
|
||||
'EMEAudioDecoder.cpp',
|
||||
'EMEDecoderModule.cpp',
|
||||
'EMEH264Decoder.cpp',
|
||||
]
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
#include "MediaTaskQueue.h"
|
||||
#include "FFmpegRuntimeLinker.h"
|
||||
|
||||
#include "FFmpegAACDecoder.h"
|
||||
#include "FFmpegAudioDecoder.h"
|
||||
|
||||
#define MAX_CHANNELS 16
|
||||
|
||||
|
@ -16,16 +16,17 @@ typedef mp4_demuxer::MP4Sample MP4Sample;
|
|||
namespace mozilla
|
||||
{
|
||||
|
||||
FFmpegAACDecoder<LIBAV_VER>::FFmpegAACDecoder(
|
||||
FFmpegAudioDecoder<LIBAV_VER>::FFmpegAudioDecoder(
|
||||
MediaTaskQueue* aTaskQueue, MediaDataDecoderCallback* aCallback,
|
||||
const mp4_demuxer::AudioDecoderConfig& aConfig)
|
||||
: FFmpegDataDecoder(aTaskQueue, AV_CODEC_ID_AAC), mCallback(aCallback)
|
||||
: FFmpegDataDecoder(aTaskQueue, GetCodecId(aConfig.mime_type))
|
||||
, mCallback(aCallback)
|
||||
{
|
||||
MOZ_COUNT_CTOR(FFmpegAACDecoder);
|
||||
MOZ_COUNT_CTOR(FFmpegAudioDecoder);
|
||||
}
|
||||
|
||||
nsresult
|
||||
FFmpegAACDecoder<LIBAV_VER>::Init()
|
||||
FFmpegAudioDecoder<LIBAV_VER>::Init()
|
||||
{
|
||||
nsresult rv = FFmpegDataDecoder::Init();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
@ -34,24 +35,44 @@ FFmpegAACDecoder<LIBAV_VER>::Init()
|
|||
}
|
||||
|
||||
static AudioDataValue*
|
||||
CopyAndPackAudio(AVFrame* aFrame, uint32_t aNumChannels, uint32_t aNumSamples)
|
||||
CopyAndPackAudio(AVFrame* aFrame, uint32_t aNumChannels, uint32_t aNumAFrames)
|
||||
{
|
||||
MOZ_ASSERT(aNumChannels <= MAX_CHANNELS);
|
||||
|
||||
nsAutoArrayPtr<AudioDataValue> audio(
|
||||
new AudioDataValue[aNumChannels * aNumSamples]);
|
||||
|
||||
AudioDataValue** data = reinterpret_cast<AudioDataValue**>(aFrame->data);
|
||||
new AudioDataValue[aNumChannels * aNumAFrames]);
|
||||
|
||||
if (aFrame->format == AV_SAMPLE_FMT_FLT) {
|
||||
// Audio data already packed. No need to do anything other than copy it
|
||||
// into a buffer we own.
|
||||
memcpy(audio, data[0], aNumChannels * aNumSamples * sizeof(AudioDataValue));
|
||||
memcpy(audio, aFrame->data[0],
|
||||
aNumChannels * aNumAFrames * sizeof(AudioDataValue));
|
||||
} else if (aFrame->format == AV_SAMPLE_FMT_FLTP) {
|
||||
// Planar audio data. Pack it into something we can understand.
|
||||
for (uint32_t channel = 0; channel < aNumChannels; channel++) {
|
||||
for (uint32_t sample = 0; sample < aNumSamples; sample++) {
|
||||
audio[sample * aNumChannels + channel] = data[channel][sample];
|
||||
AudioDataValue* tmp = audio;
|
||||
AudioDataValue** data = reinterpret_cast<AudioDataValue**>(aFrame->data);
|
||||
for (uint32_t frame = 0; frame < aNumAFrames; frame++) {
|
||||
for (uint32_t channel = 0; channel < aNumChannels; channel++) {
|
||||
*tmp++ = data[channel][frame];
|
||||
}
|
||||
}
|
||||
} else if (aFrame->format == AV_SAMPLE_FMT_S16) {
|
||||
// Audio data already packed. Need to convert from S16 to 32 bits Float
|
||||
AudioDataValue* tmp = audio;
|
||||
int16_t* data = reinterpret_cast<int16_t**>(aFrame->data)[0];
|
||||
for (uint32_t frame = 0; frame < aNumAFrames; frame++) {
|
||||
for (uint32_t channel = 0; channel < aNumChannels; channel++) {
|
||||
*tmp++ = AudioSampleToFloat(*data++);
|
||||
}
|
||||
}
|
||||
} else if (aFrame->format == AV_SAMPLE_FMT_S16P) {
|
||||
// Planar audio data. Convert it from S16 to 32 bits float
|
||||
// and pack it into something we can understand.
|
||||
AudioDataValue* tmp = audio;
|
||||
int16_t** data = reinterpret_cast<int16_t**>(aFrame->data);
|
||||
for (uint32_t frame = 0; frame < aNumAFrames; frame++) {
|
||||
for (uint32_t channel = 0; channel < aNumChannels; channel++) {
|
||||
*tmp++ = AudioSampleToFloat(data[channel][frame]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -60,7 +81,7 @@ CopyAndPackAudio(AVFrame* aFrame, uint32_t aNumChannels, uint32_t aNumSamples)
|
|||
}
|
||||
|
||||
void
|
||||
FFmpegAACDecoder<LIBAV_VER>::DecodePacket(MP4Sample* aSample)
|
||||
FFmpegAudioDecoder<LIBAV_VER>::DecodePacket(MP4Sample* aSample)
|
||||
{
|
||||
AVPacket packet;
|
||||
av_init_packet(&packet);
|
||||
|
@ -107,24 +128,38 @@ FFmpegAACDecoder<LIBAV_VER>::DecodePacket(MP4Sample* aSample)
|
|||
}
|
||||
|
||||
nsresult
|
||||
FFmpegAACDecoder<LIBAV_VER>::Input(MP4Sample* aSample)
|
||||
FFmpegAudioDecoder<LIBAV_VER>::Input(MP4Sample* aSample)
|
||||
{
|
||||
mTaskQueue->Dispatch(NS_NewRunnableMethodWithArg<nsAutoPtr<MP4Sample> >(
|
||||
this, &FFmpegAACDecoder::DecodePacket, nsAutoPtr<MP4Sample>(aSample)));
|
||||
this, &FFmpegAudioDecoder::DecodePacket, nsAutoPtr<MP4Sample>(aSample)));
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
FFmpegAACDecoder<LIBAV_VER>::Drain()
|
||||
FFmpegAudioDecoder<LIBAV_VER>::Drain()
|
||||
{
|
||||
mCallback->DrainComplete();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
FFmpegAACDecoder<LIBAV_VER>::~FFmpegAACDecoder()
|
||||
AVCodecID
|
||||
FFmpegAudioDecoder<LIBAV_VER>::GetCodecId(const char* aMimeType)
|
||||
{
|
||||
MOZ_COUNT_DTOR(FFmpegAACDecoder);
|
||||
if (!strcmp(aMimeType, "audio/mpeg")) {
|
||||
return AV_CODEC_ID_MP3;
|
||||
}
|
||||
|
||||
if (!strcmp(aMimeType, "audio/mp4a-latm")) {
|
||||
return AV_CODEC_ID_AAC;
|
||||
}
|
||||
|
||||
return AV_CODEC_ID_NONE;
|
||||
}
|
||||
|
||||
FFmpegAudioDecoder<LIBAV_VER>::~FFmpegAudioDecoder()
|
||||
{
|
||||
MOZ_COUNT_DTOR(FFmpegAudioDecoder);
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
|
@ -13,22 +13,23 @@
|
|||
namespace mozilla
|
||||
{
|
||||
|
||||
template <int V> class FFmpegAACDecoder
|
||||
template <int V> class FFmpegAudioDecoder
|
||||
{
|
||||
};
|
||||
|
||||
template <>
|
||||
class FFmpegAACDecoder<LIBAV_VER> : public FFmpegDataDecoder<LIBAV_VER>
|
||||
class FFmpegAudioDecoder<LIBAV_VER> : public FFmpegDataDecoder<LIBAV_VER>
|
||||
{
|
||||
public:
|
||||
FFmpegAACDecoder(MediaTaskQueue* aTaskQueue,
|
||||
MediaDataDecoderCallback* aCallback,
|
||||
const mp4_demuxer::AudioDecoderConfig& aConfig);
|
||||
virtual ~FFmpegAACDecoder();
|
||||
FFmpegAudioDecoder(MediaTaskQueue* aTaskQueue,
|
||||
MediaDataDecoderCallback* aCallback,
|
||||
const mp4_demuxer::AudioDecoderConfig& aConfig);
|
||||
virtual ~FFmpegAudioDecoder();
|
||||
|
||||
virtual nsresult Init() MOZ_OVERRIDE;
|
||||
virtual nsresult Input(mp4_demuxer::MP4Sample* aSample) MOZ_OVERRIDE;
|
||||
virtual nsresult Drain() MOZ_OVERRIDE;
|
||||
static AVCodecID GetCodecId(const char* aMimeType);
|
||||
|
||||
private:
|
||||
void DecodePacket(mp4_demuxer::MP4Sample* aSample);
|
|
@ -108,8 +108,10 @@ FFmpegDataDecoder<LIBAV_VER>::Init()
|
|||
|
||||
if (mCodecContext->codec_type == AVMEDIA_TYPE_AUDIO &&
|
||||
mCodecContext->sample_fmt != AV_SAMPLE_FMT_FLT &&
|
||||
mCodecContext->sample_fmt != AV_SAMPLE_FMT_FLTP) {
|
||||
NS_WARNING("FFmpeg AAC decoder outputs unsupported audio format.");
|
||||
mCodecContext->sample_fmt != AV_SAMPLE_FMT_FLTP &&
|
||||
mCodecContext->sample_fmt != AV_SAMPLE_FMT_S16 &&
|
||||
mCodecContext->sample_fmt != AV_SAMPLE_FMT_S16P) {
|
||||
NS_WARNING("FFmpeg audio decoder outputs unsupported audio format.");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
#define __FFmpegDecoderModule_h__
|
||||
|
||||
#include "PlatformDecoderModule.h"
|
||||
#include "FFmpegAACDecoder.h"
|
||||
#include "FFmpegAudioDecoder.h"
|
||||
#include "FFmpegH264Decoder.h"
|
||||
|
||||
namespace mozilla
|
||||
|
@ -39,14 +39,19 @@ public:
|
|||
}
|
||||
|
||||
virtual already_AddRefed<MediaDataDecoder>
|
||||
CreateAACDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
|
||||
MediaTaskQueue* aAudioTaskQueue,
|
||||
MediaDataDecoderCallback* aCallback) MOZ_OVERRIDE
|
||||
CreateAudioDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
|
||||
MediaTaskQueue* aAudioTaskQueue,
|
||||
MediaDataDecoderCallback* aCallback) MOZ_OVERRIDE
|
||||
{
|
||||
nsRefPtr<MediaDataDecoder> decoder =
|
||||
new FFmpegAACDecoder<V>(aAudioTaskQueue, aCallback, aConfig);
|
||||
new FFmpegAudioDecoder<V>(aAudioTaskQueue, aCallback, aConfig);
|
||||
return decoder.forget();
|
||||
}
|
||||
|
||||
virtual bool SupportsAudioMimeType(const char* aMimeType) MOZ_OVERRIDE
|
||||
{
|
||||
return FFmpegAudioDecoder<V>::GetCodecId(aMimeType) != AV_CODEC_ID_NONE;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -18,6 +18,8 @@ extern "C" {
|
|||
#if LIBAVCODEC_VERSION_MAJOR < 55
|
||||
#define AV_CODEC_ID_H264 CODEC_ID_H264
|
||||
#define AV_CODEC_ID_AAC CODEC_ID_AAC
|
||||
#define AV_CODEC_ID_MP3 CODEC_ID_MP3
|
||||
#define AV_CODEC_ID_NONE CODEC_ID_NONE
|
||||
typedef CodecID AVCodecID;
|
||||
#endif
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'../FFmpegAACDecoder.cpp',
|
||||
'../FFmpegAudioDecoder.cpp',
|
||||
'../FFmpegDataDecoder.cpp',
|
||||
'../FFmpegDecoderModule.cpp',
|
||||
'../FFmpegH264Decoder.cpp',
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'../FFmpegAACDecoder.cpp',
|
||||
'../FFmpegAudioDecoder.cpp',
|
||||
'../FFmpegDataDecoder.cpp',
|
||||
'../FFmpegDecoderModule.cpp',
|
||||
'../FFmpegH264Decoder.cpp',
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'../FFmpegAACDecoder.cpp',
|
||||
'../FFmpegAudioDecoder.cpp',
|
||||
'../FFmpegDataDecoder.cpp',
|
||||
'../FFmpegDecoderModule.cpp',
|
||||
'../FFmpegH264Decoder.cpp',
|
||||
|
|
|
@ -48,9 +48,9 @@ GonkDecoderModule::CreateH264Decoder(const mp4_demuxer::VideoDecoderConfig& aCon
|
|||
}
|
||||
|
||||
already_AddRefed<MediaDataDecoder>
|
||||
GonkDecoderModule::CreateAACDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
|
||||
MediaTaskQueue* aAudioTaskQueue,
|
||||
MediaDataDecoderCallback* aCallback)
|
||||
GonkDecoderModule::CreateAudioDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
|
||||
MediaTaskQueue* aAudioTaskQueue,
|
||||
MediaDataDecoderCallback* aCallback)
|
||||
{
|
||||
nsRefPtr<MediaDataDecoder> decoder =
|
||||
new GonkMediaDataDecoder(new GonkAudioDecoderManager(aConfig), aAudioTaskQueue,
|
||||
|
|
|
@ -29,9 +29,9 @@ public:
|
|||
|
||||
// Decode thread.
|
||||
virtual already_AddRefed<MediaDataDecoder>
|
||||
CreateAACDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
|
||||
MediaTaskQueue* aAudioTaskQueue,
|
||||
MediaDataDecoderCallback* aCallback) MOZ_OVERRIDE;
|
||||
CreateAudioDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
|
||||
MediaTaskQueue* aAudioTaskQueue,
|
||||
MediaDataDecoderCallback* aCallback) MOZ_OVERRIDE;
|
||||
|
||||
static void Init();
|
||||
};
|
||||
|
|
|
@ -81,9 +81,9 @@ WMFDecoderModule::CreateH264Decoder(const mp4_demuxer::VideoDecoderConfig& aConf
|
|||
}
|
||||
|
||||
already_AddRefed<MediaDataDecoder>
|
||||
WMFDecoderModule::CreateAACDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
|
||||
MediaTaskQueue* aAudioTaskQueue,
|
||||
MediaDataDecoderCallback* aCallback)
|
||||
WMFDecoderModule::CreateAudioDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
|
||||
MediaTaskQueue* aAudioTaskQueue,
|
||||
MediaDataDecoderCallback* aCallback)
|
||||
{
|
||||
nsRefPtr<MediaDataDecoder> decoder =
|
||||
new WMFMediaDataDecoder(new WMFAudioMFTManager(aConfig),
|
||||
|
|
|
@ -30,9 +30,9 @@ public:
|
|||
MediaDataDecoderCallback* aCallback) MOZ_OVERRIDE;
|
||||
|
||||
virtual already_AddRefed<MediaDataDecoder>
|
||||
CreateAACDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
|
||||
MediaTaskQueue* aAudioTaskQueue,
|
||||
MediaDataDecoderCallback* aCallback) MOZ_OVERRIDE;
|
||||
CreateAudioDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
|
||||
MediaTaskQueue* aAudioTaskQueue,
|
||||
MediaDataDecoderCallback* aCallback) MOZ_OVERRIDE;
|
||||
|
||||
// Called on main thread.
|
||||
static void Init();
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "mp4_demuxer/DecoderData.h"
|
||||
#include "media/stagefright/MetaData.h"
|
||||
#include "media/stagefright/MediaBuffer.h"
|
||||
#include "media/stagefright/MediaDefs.h"
|
||||
#include "media/stagefright/Utils.h"
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "include/ESDS.h"
|
||||
|
@ -159,7 +160,7 @@ bool
|
|||
AudioDecoderConfig::IsValid()
|
||||
{
|
||||
return channel_count > 0 && samples_per_second > 0 && frequency_index > 0 &&
|
||||
aac_profile > 0;
|
||||
(mime_type != MEDIA_MIMETYPE_AUDIO_AAC || aac_profile > 0);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include "include/MPEG4Extractor.h"
|
||||
#include "media/stagefright/DataSource.h"
|
||||
#include "media/stagefright/MediaDefs.h"
|
||||
#include "media/stagefright/MediaSource.h"
|
||||
#include "media/stagefright/MetaData.h"
|
||||
#include "mp4_demuxer/Adts.h"
|
||||
|
@ -168,10 +169,12 @@ MP4Demuxer::DemuxAudioSample()
|
|||
}
|
||||
|
||||
sample->Update();
|
||||
if (!Adts::ConvertEsdsToAdts(mAudioConfig.channel_count,
|
||||
mAudioConfig.frequency_index,
|
||||
mAudioConfig.aac_profile, sample)) {
|
||||
return nullptr;
|
||||
if (!strcmp(mAudioConfig.mime_type, MEDIA_MIMETYPE_AUDIO_AAC)) {
|
||||
if (!Adts::ConvertEsdsToAdts(mAudioConfig.channel_count,
|
||||
mAudioConfig.frequency_index,
|
||||
mAudioConfig.aac_profile, sample)) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return sample.forget();
|
||||
|
|
|
@ -2293,10 +2293,7 @@ status_t MPEG4Extractor::updateAudioTrackInfoFromESDS_MPEG4Audio(
|
|||
|
||||
if (objectTypeIndication == 0x6b) {
|
||||
// The media subtype is MP3 audio
|
||||
// Our software MP3 audio decoder may not be able to handle
|
||||
// packetized MP3 audio; for now, lets just return ERROR_UNSUPPORTED
|
||||
ALOGE("MP3 track in MP4/3GPP file is not supported");
|
||||
return ERROR_UNSUPPORTED;
|
||||
mLastTrack->meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MPEG);
|
||||
}
|
||||
|
||||
const uint8_t *csd;
|
||||
|
|
Загрузка…
Ссылка в новой задаче