Merge "In order to recover from video lagging behind audio, drop avc frames"
This commit is contained in:
committed by
Android (Google) Code Review
commit
c7342fbf99
@@ -34,17 +34,21 @@
|
|||||||
#include <media/stagefright/foundation/ADebug.h>
|
#include <media/stagefright/foundation/ADebug.h>
|
||||||
#include <media/stagefright/foundation/AMessage.h>
|
#include <media/stagefright/foundation/AMessage.h>
|
||||||
#include <media/stagefright/ACodec.h>
|
#include <media/stagefright/ACodec.h>
|
||||||
|
#include <media/stagefright/MediaDefs.h>
|
||||||
#include <media/stagefright/MediaErrors.h>
|
#include <media/stagefright/MediaErrors.h>
|
||||||
#include <media/stagefright/MetaData.h>
|
#include <media/stagefright/MetaData.h>
|
||||||
#include <surfaceflinger/Surface.h>
|
#include <surfaceflinger/Surface.h>
|
||||||
#include <gui/ISurfaceTexture.h>
|
#include <gui/ISurfaceTexture.h>
|
||||||
|
|
||||||
|
#include "avc_utils.h"
|
||||||
|
|
||||||
namespace android {
|
namespace android {
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
NuPlayer::NuPlayer()
|
NuPlayer::NuPlayer()
|
||||||
: mUIDValid(false),
|
: mUIDValid(false),
|
||||||
|
mVideoIsAVC(false),
|
||||||
mAudioEOS(false),
|
mAudioEOS(false),
|
||||||
mVideoEOS(false),
|
mVideoEOS(false),
|
||||||
mScanSourcesPending(false),
|
mScanSourcesPending(false),
|
||||||
@@ -52,7 +56,12 @@ NuPlayer::NuPlayer()
|
|||||||
mFlushingAudio(NONE),
|
mFlushingAudio(NONE),
|
||||||
mFlushingVideo(NONE),
|
mFlushingVideo(NONE),
|
||||||
mResetInProgress(false),
|
mResetInProgress(false),
|
||||||
mResetPostponed(false) {
|
mResetPostponed(false),
|
||||||
|
mSkipRenderingAudioUntilMediaTimeUs(-1ll),
|
||||||
|
mSkipRenderingVideoUntilMediaTimeUs(-1ll),
|
||||||
|
mVideoLateByUs(0ll),
|
||||||
|
mNumFramesTotal(0ll),
|
||||||
|
mNumFramesDropped(0ll) {
|
||||||
}
|
}
|
||||||
|
|
||||||
NuPlayer::~NuPlayer() {
|
NuPlayer::~NuPlayer() {
|
||||||
@@ -185,10 +194,14 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) {
|
|||||||
{
|
{
|
||||||
LOGV("kWhatStart");
|
LOGV("kWhatStart");
|
||||||
|
|
||||||
|
mVideoIsAVC = false;
|
||||||
mAudioEOS = false;
|
mAudioEOS = false;
|
||||||
mVideoEOS = false;
|
mVideoEOS = false;
|
||||||
mSkipRenderingAudioUntilMediaTimeUs = -1;
|
mSkipRenderingAudioUntilMediaTimeUs = -1;
|
||||||
mSkipRenderingVideoUntilMediaTimeUs = -1;
|
mSkipRenderingVideoUntilMediaTimeUs = -1;
|
||||||
|
mVideoLateByUs = 0;
|
||||||
|
mNumFramesTotal = 0;
|
||||||
|
mNumFramesDropped = 0;
|
||||||
|
|
||||||
mSource->start();
|
mSource->start();
|
||||||
|
|
||||||
@@ -269,6 +282,8 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) {
|
|||||||
} else {
|
} else {
|
||||||
CHECK(IsFlushingState(mFlushingVideo, &needShutdown));
|
CHECK(IsFlushingState(mFlushingVideo, &needShutdown));
|
||||||
mFlushingVideo = FLUSHED;
|
mFlushingVideo = FLUSHED;
|
||||||
|
|
||||||
|
mVideoLateByUs = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGV("decoder %s flush completed", audio ? "audio" : "video");
|
LOGV("decoder %s flush completed", audio ? "audio" : "video");
|
||||||
@@ -397,13 +412,18 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) {
|
|||||||
int64_t positionUs;
|
int64_t positionUs;
|
||||||
CHECK(msg->findInt64("positionUs", &positionUs));
|
CHECK(msg->findInt64("positionUs", &positionUs));
|
||||||
|
|
||||||
|
CHECK(msg->findInt64("videoLateByUs", &mVideoLateByUs));
|
||||||
|
|
||||||
if (mDriver != NULL) {
|
if (mDriver != NULL) {
|
||||||
sp<NuPlayerDriver> driver = mDriver.promote();
|
sp<NuPlayerDriver> driver = mDriver.promote();
|
||||||
if (driver != NULL) {
|
if (driver != NULL) {
|
||||||
driver->notifyPosition(positionUs);
|
driver->notifyPosition(positionUs);
|
||||||
|
|
||||||
|
driver->notifyFrameStats(
|
||||||
|
mNumFramesTotal, mNumFramesDropped);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else if (what == Renderer::kWhatFlushComplete) {
|
||||||
CHECK_EQ(what, (int32_t)Renderer::kWhatFlushComplete);
|
CHECK_EQ(what, (int32_t)Renderer::kWhatFlushComplete);
|
||||||
|
|
||||||
int32_t audio;
|
int32_t audio;
|
||||||
@@ -565,6 +585,12 @@ status_t NuPlayer::instantiateDecoder(bool audio, sp<Decoder> *decoder) {
|
|||||||
return -EWOULDBLOCK;
|
return -EWOULDBLOCK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!audio) {
|
||||||
|
const char *mime;
|
||||||
|
CHECK(meta->findCString(kKeyMIMEType, &mime));
|
||||||
|
mVideoIsAVC = !strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime);
|
||||||
|
}
|
||||||
|
|
||||||
sp<AMessage> notify =
|
sp<AMessage> notify =
|
||||||
new AMessage(audio ? kWhatAudioNotify : kWhatVideoNotify,
|
new AMessage(audio ? kWhatAudioNotify : kWhatVideoNotify,
|
||||||
id());
|
id());
|
||||||
@@ -598,53 +624,70 @@ status_t NuPlayer::feedDecoderInputData(bool audio, const sp<AMessage> &msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sp<ABuffer> accessUnit;
|
sp<ABuffer> accessUnit;
|
||||||
status_t err = mSource->dequeueAccessUnit(audio, &accessUnit);
|
|
||||||
|
|
||||||
if (err == -EWOULDBLOCK) {
|
bool dropAccessUnit;
|
||||||
return err;
|
do {
|
||||||
} else if (err != OK) {
|
status_t err = mSource->dequeueAccessUnit(audio, &accessUnit);
|
||||||
if (err == INFO_DISCONTINUITY) {
|
|
||||||
int32_t type;
|
|
||||||
CHECK(accessUnit->meta()->findInt32("discontinuity", &type));
|
|
||||||
|
|
||||||
bool formatChange =
|
if (err == -EWOULDBLOCK) {
|
||||||
type == ATSParser::DISCONTINUITY_FORMATCHANGE;
|
return err;
|
||||||
|
} else if (err != OK) {
|
||||||
|
if (err == INFO_DISCONTINUITY) {
|
||||||
|
int32_t type;
|
||||||
|
CHECK(accessUnit->meta()->findInt32("discontinuity", &type));
|
||||||
|
|
||||||
LOGV("%s discontinuity (formatChange=%d)",
|
bool formatChange =
|
||||||
audio ? "audio" : "video", formatChange);
|
type == ATSParser::DISCONTINUITY_FORMATCHANGE;
|
||||||
|
|
||||||
if (audio) {
|
LOGV("%s discontinuity (formatChange=%d)",
|
||||||
mSkipRenderingAudioUntilMediaTimeUs = -1;
|
audio ? "audio" : "video", formatChange);
|
||||||
} else {
|
|
||||||
mSkipRenderingVideoUntilMediaTimeUs = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
sp<AMessage> extra;
|
if (audio) {
|
||||||
if (accessUnit->meta()->findMessage("extra", &extra)
|
mSkipRenderingAudioUntilMediaTimeUs = -1;
|
||||||
&& extra != NULL) {
|
} else {
|
||||||
int64_t resumeAtMediaTimeUs;
|
mSkipRenderingVideoUntilMediaTimeUs = -1;
|
||||||
if (extra->findInt64(
|
}
|
||||||
"resume-at-mediatimeUs", &resumeAtMediaTimeUs)) {
|
|
||||||
LOGI("suppressing rendering of %s until %lld us",
|
|
||||||
audio ? "audio" : "video", resumeAtMediaTimeUs);
|
|
||||||
|
|
||||||
if (audio) {
|
sp<AMessage> extra;
|
||||||
mSkipRenderingAudioUntilMediaTimeUs =
|
if (accessUnit->meta()->findMessage("extra", &extra)
|
||||||
resumeAtMediaTimeUs;
|
&& extra != NULL) {
|
||||||
} else {
|
int64_t resumeAtMediaTimeUs;
|
||||||
mSkipRenderingVideoUntilMediaTimeUs =
|
if (extra->findInt64(
|
||||||
resumeAtMediaTimeUs;
|
"resume-at-mediatimeUs", &resumeAtMediaTimeUs)) {
|
||||||
|
LOGI("suppressing rendering of %s until %lld us",
|
||||||
|
audio ? "audio" : "video", resumeAtMediaTimeUs);
|
||||||
|
|
||||||
|
if (audio) {
|
||||||
|
mSkipRenderingAudioUntilMediaTimeUs =
|
||||||
|
resumeAtMediaTimeUs;
|
||||||
|
} else {
|
||||||
|
mSkipRenderingVideoUntilMediaTimeUs =
|
||||||
|
resumeAtMediaTimeUs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
flushDecoder(audio, formatChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
flushDecoder(audio, formatChange);
|
reply->setInt32("err", err);
|
||||||
|
reply->post();
|
||||||
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
reply->setInt32("err", err);
|
if (!audio) {
|
||||||
reply->post();
|
++mNumFramesTotal;
|
||||||
return OK;
|
}
|
||||||
}
|
|
||||||
|
dropAccessUnit = false;
|
||||||
|
if (!audio
|
||||||
|
&& mVideoLateByUs > 100000ll
|
||||||
|
&& mVideoIsAVC
|
||||||
|
&& !IsAVCReferenceFrame(accessUnit)) {
|
||||||
|
dropAccessUnit = true;
|
||||||
|
++mNumFramesDropped;
|
||||||
|
}
|
||||||
|
} while (dropAccessUnit);
|
||||||
|
|
||||||
// LOGV("returned a valid buffer of %s data", audio ? "audio" : "video");
|
// LOGV("returned a valid buffer of %s data", audio ? "audio" : "video");
|
||||||
|
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ private:
|
|||||||
sp<NativeWindowWrapper> mNativeWindow;
|
sp<NativeWindowWrapper> mNativeWindow;
|
||||||
sp<MediaPlayerBase::AudioSink> mAudioSink;
|
sp<MediaPlayerBase::AudioSink> mAudioSink;
|
||||||
sp<Decoder> mVideoDecoder;
|
sp<Decoder> mVideoDecoder;
|
||||||
|
bool mVideoIsAVC;
|
||||||
sp<Decoder> mAudioDecoder;
|
sp<Decoder> mAudioDecoder;
|
||||||
sp<Renderer> mRenderer;
|
sp<Renderer> mRenderer;
|
||||||
|
|
||||||
@@ -119,6 +120,9 @@ private:
|
|||||||
int64_t mSkipRenderingAudioUntilMediaTimeUs;
|
int64_t mSkipRenderingAudioUntilMediaTimeUs;
|
||||||
int64_t mSkipRenderingVideoUntilMediaTimeUs;
|
int64_t mSkipRenderingVideoUntilMediaTimeUs;
|
||||||
|
|
||||||
|
int64_t mVideoLateByUs;
|
||||||
|
int64_t mNumFramesTotal, mNumFramesDropped;
|
||||||
|
|
||||||
status_t instantiateDecoder(bool audio, sp<Decoder> *decoder);
|
status_t instantiateDecoder(bool audio, sp<Decoder> *decoder);
|
||||||
|
|
||||||
status_t feedDecoderInputData(bool audio, const sp<AMessage> &msg);
|
status_t feedDecoderInputData(bool audio, const sp<AMessage> &msg);
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ NuPlayerDriver::NuPlayerDriver()
|
|||||||
: mResetInProgress(false),
|
: mResetInProgress(false),
|
||||||
mDurationUs(-1),
|
mDurationUs(-1),
|
||||||
mPositionUs(-1),
|
mPositionUs(-1),
|
||||||
|
mNumFramesTotal(0),
|
||||||
|
mNumFramesDropped(0),
|
||||||
mLooper(new ALooper),
|
mLooper(new ALooper),
|
||||||
mState(UNINITIALIZED),
|
mState(UNINITIALIZED),
|
||||||
mStartupSeekTimeUs(-1) {
|
mStartupSeekTimeUs(-1) {
|
||||||
@@ -292,4 +294,30 @@ void NuPlayerDriver::notifySeekComplete() {
|
|||||||
sendEvent(MEDIA_SEEK_COMPLETE);
|
sendEvent(MEDIA_SEEK_COMPLETE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NuPlayerDriver::notifyFrameStats(
|
||||||
|
int64_t numFramesTotal, int64_t numFramesDropped) {
|
||||||
|
Mutex::Autolock autoLock(mLock);
|
||||||
|
mNumFramesTotal = numFramesTotal;
|
||||||
|
mNumFramesDropped = numFramesDropped;
|
||||||
|
}
|
||||||
|
|
||||||
|
status_t NuPlayerDriver::dump(int fd, const Vector<String16> &args) const {
|
||||||
|
Mutex::Autolock autoLock(mLock);
|
||||||
|
|
||||||
|
FILE *out = fdopen(dup(fd), "w");
|
||||||
|
|
||||||
|
fprintf(out, " NuPlayer\n");
|
||||||
|
fprintf(out, " numFramesTotal(%lld), numFramesDropped(%lld), "
|
||||||
|
"percentageDropped(%.2f)\n",
|
||||||
|
mNumFramesTotal,
|
||||||
|
mNumFramesDropped,
|
||||||
|
mNumFramesTotal == 0
|
||||||
|
? 0.0 : (double)mNumFramesDropped / mNumFramesTotal);
|
||||||
|
|
||||||
|
fclose(out);
|
||||||
|
out = NULL;
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace android
|
} // namespace android
|
||||||
|
|||||||
@@ -60,16 +60,19 @@ struct NuPlayerDriver : public MediaPlayerInterface {
|
|||||||
virtual status_t getMetadata(
|
virtual status_t getMetadata(
|
||||||
const media::Metadata::Filter& ids, Parcel *records);
|
const media::Metadata::Filter& ids, Parcel *records);
|
||||||
|
|
||||||
|
virtual status_t dump(int fd, const Vector<String16> &args) const;
|
||||||
|
|
||||||
void notifyResetComplete();
|
void notifyResetComplete();
|
||||||
void notifyDuration(int64_t durationUs);
|
void notifyDuration(int64_t durationUs);
|
||||||
void notifyPosition(int64_t positionUs);
|
void notifyPosition(int64_t positionUs);
|
||||||
void notifySeekComplete();
|
void notifySeekComplete();
|
||||||
|
void notifyFrameStats(int64_t numFramesTotal, int64_t numFramesDropped);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual ~NuPlayerDriver();
|
virtual ~NuPlayerDriver();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Mutex mLock;
|
mutable Mutex mLock;
|
||||||
Condition mCondition;
|
Condition mCondition;
|
||||||
|
|
||||||
// The following are protected through "mLock"
|
// The following are protected through "mLock"
|
||||||
@@ -77,6 +80,8 @@ private:
|
|||||||
bool mResetInProgress;
|
bool mResetInProgress;
|
||||||
int64_t mDurationUs;
|
int64_t mDurationUs;
|
||||||
int64_t mPositionUs;
|
int64_t mPositionUs;
|
||||||
|
int64_t mNumFramesTotal;
|
||||||
|
int64_t mNumFramesDropped;
|
||||||
// <<<
|
// <<<
|
||||||
|
|
||||||
sp<ALooper> mLooper;
|
sp<ALooper> mLooper;
|
||||||
|
|||||||
@@ -47,7 +47,8 @@ NuPlayer::Renderer::Renderer(
|
|||||||
mHasVideo(false),
|
mHasVideo(false),
|
||||||
mSyncQueues(false),
|
mSyncQueues(false),
|
||||||
mPaused(false),
|
mPaused(false),
|
||||||
mLastPositionUpdateUs(-1ll) {
|
mLastPositionUpdateUs(-1ll),
|
||||||
|
mVideoLateByUs(0ll) {
|
||||||
}
|
}
|
||||||
|
|
||||||
NuPlayer::Renderer::~Renderer() {
|
NuPlayer::Renderer::~Renderer() {
|
||||||
@@ -357,22 +358,26 @@ void NuPlayer::Renderer::onDrainVideoQueue() {
|
|||||||
|
|
||||||
mVideoQueue.erase(mVideoQueue.begin());
|
mVideoQueue.erase(mVideoQueue.begin());
|
||||||
entry = NULL;
|
entry = NULL;
|
||||||
|
|
||||||
|
mVideoLateByUs = 0ll;
|
||||||
|
|
||||||
|
notifyPosition();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0
|
|
||||||
int64_t mediaTimeUs;
|
int64_t mediaTimeUs;
|
||||||
CHECK(entry->mBuffer->meta()->findInt64("timeUs", &mediaTimeUs));
|
CHECK(entry->mBuffer->meta()->findInt64("timeUs", &mediaTimeUs));
|
||||||
|
|
||||||
int64_t realTimeUs = mediaTimeUs - mAnchorTimeMediaUs + mAnchorTimeRealUs;
|
int64_t realTimeUs = mediaTimeUs - mAnchorTimeMediaUs + mAnchorTimeRealUs;
|
||||||
int64_t lateByUs = ALooper::GetNowUs() - realTimeUs;
|
mVideoLateByUs = ALooper::GetNowUs() - realTimeUs;
|
||||||
|
|
||||||
if (lateByUs > 40000) {
|
bool tooLate = (mVideoLateByUs > 40000);
|
||||||
LOGI("video late by %lld us (%.2f secs)", lateByUs, lateByUs / 1E6);
|
|
||||||
|
if (tooLate) {
|
||||||
|
LOGV("video late by %lld us (%.2f secs)", lateByUs, lateByUs / 1E6);
|
||||||
} else {
|
} else {
|
||||||
LOGV("rendering video at media time %.2f secs", mediaTimeUs / 1E6);
|
LOGV("rendering video at media time %.2f secs", mediaTimeUs / 1E6);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
entry->mNotifyConsumed->setInt32("render", true);
|
entry->mNotifyConsumed->setInt32("render", true);
|
||||||
entry->mNotifyConsumed->post();
|
entry->mNotifyConsumed->post();
|
||||||
@@ -604,6 +609,7 @@ void NuPlayer::Renderer::notifyPosition() {
|
|||||||
sp<AMessage> notify = mNotify->dup();
|
sp<AMessage> notify = mNotify->dup();
|
||||||
notify->setInt32("what", kWhatPosition);
|
notify->setInt32("what", kWhatPosition);
|
||||||
notify->setInt64("positionUs", positionUs);
|
notify->setInt64("positionUs", positionUs);
|
||||||
|
notify->setInt64("videoLateByUs", mVideoLateByUs);
|
||||||
notify->post();
|
notify->post();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ private:
|
|||||||
bool mPaused;
|
bool mPaused;
|
||||||
|
|
||||||
int64_t mLastPositionUpdateUs;
|
int64_t mLastPositionUpdateUs;
|
||||||
|
int64_t mVideoLateByUs;
|
||||||
|
|
||||||
bool onDrainAudioQueue();
|
bool onDrainAudioQueue();
|
||||||
void postDrainAudioQueue(int64_t delayUs = 0);
|
void postDrainAudioQueue(int64_t delayUs = 0);
|
||||||
@@ -118,6 +119,7 @@ private:
|
|||||||
void notifyEOS(bool audio, status_t finalResult);
|
void notifyEOS(bool audio, status_t finalResult);
|
||||||
void notifyFlushComplete(bool audio);
|
void notifyFlushComplete(bool audio);
|
||||||
void notifyPosition();
|
void notifyPosition();
|
||||||
|
void notifyVideoLateBy(int64_t lateByUs);
|
||||||
|
|
||||||
void flushQueue(List<QueueEntry> *queue);
|
void flushQueue(List<QueueEntry> *queue);
|
||||||
bool dropBufferWhileFlushing(bool audio, const sp<AMessage> &msg);
|
bool dropBufferWhileFlushing(bool audio, const sp<AMessage> &msg);
|
||||||
|
|||||||
@@ -329,6 +329,28 @@ bool IsIDR(const sp<ABuffer> &buffer) {
|
|||||||
return foundIDR;
|
return foundIDR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsAVCReferenceFrame(const sp<ABuffer> &accessUnit) {
|
||||||
|
const uint8_t *data = accessUnit->data();
|
||||||
|
size_t size = accessUnit->size();
|
||||||
|
|
||||||
|
const uint8_t *nalStart;
|
||||||
|
size_t nalSize;
|
||||||
|
while (getNextNALUnit(&data, &size, &nalStart, &nalSize, true) == OK) {
|
||||||
|
CHECK_GT(nalSize, 0u);
|
||||||
|
|
||||||
|
unsigned nalType = nalStart[0] & 0x1f;
|
||||||
|
|
||||||
|
if (nalType == 5) {
|
||||||
|
return true;
|
||||||
|
} else if (nalType == 1) {
|
||||||
|
unsigned nal_ref_idc = (nalStart[0] >> 5) & 3;
|
||||||
|
return nal_ref_idc != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
sp<MetaData> MakeAACCodecSpecificData(
|
sp<MetaData> MakeAACCodecSpecificData(
|
||||||
unsigned profile, unsigned sampling_freq_index,
|
unsigned profile, unsigned sampling_freq_index,
|
||||||
unsigned channel_configuration) {
|
unsigned channel_configuration) {
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ struct MetaData;
|
|||||||
sp<MetaData> MakeAVCCodecSpecificData(const sp<ABuffer> &accessUnit);
|
sp<MetaData> MakeAVCCodecSpecificData(const sp<ABuffer> &accessUnit);
|
||||||
|
|
||||||
bool IsIDR(const sp<ABuffer> &accessUnit);
|
bool IsIDR(const sp<ABuffer> &accessUnit);
|
||||||
|
bool IsAVCReferenceFrame(const sp<ABuffer> &accessUnit);
|
||||||
|
|
||||||
const char *AVCProfileToString(uint8_t profile);
|
const char *AVCProfileToString(uint8_t profile);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user