X-Git-Url: http://git.osdn.net/view?a=blobdiff_plain;f=libstagefright%2FFFmpegExtractor%2FFFmpegExtractor.cpp;h=08fa7a2b78e74d28227f188d0eff4750f170562e;hb=4fcb5f072bf1232475a99b428ad543d8342449aa;hp=8e94180d2e4ffe7d52ab0a7e1a60902a9fb2ce75;hpb=b8f32261fdbba4fb452cd8cbd2349c261e431421;p=android-x86%2Fexternal-stagefright-plugins.git diff --git a/libstagefright/FFmpegExtractor/FFmpegExtractor.cpp b/libstagefright/FFmpegExtractor/FFmpegExtractor.cpp index 8e94180..08fa7a2 100644 --- a/libstagefright/FFmpegExtractor/FFmpegExtractor.cpp +++ b/libstagefright/FFmpegExtractor/FFmpegExtractor.cpp @@ -18,7 +18,9 @@ #define LOG_TAG "FFmpegExtractor" #include +#include #include /* INT_MAX */ +#include #include #include @@ -27,32 +29,35 @@ #include #include #include -#include +#include #include #include #include #include #include +#include #include #include #include "include/avc_utils.h" -#include "ffmpeg_utils/ffmpeg_utils.h" +#include "utils/ffmpeg_utils.h" +#include "utils/ffmpeg_cmdutils.h" #include "FFmpegExtractor.h" -#define DEBUG_READ_ENTRY 0 -#define DIABLE_VIDEO 0 -#define DIABLE_AUDIO 0 -#define WAIT_KEY_PACKET_AFTER_SEEK 1 -#define DISABLE_NAL_TO_ANNEXB 0 - #define MAX_QUEUE_SIZE (15 * 1024 * 1024) #define MIN_AUDIOQ_SIZE (20 * 16 * 1024) #define MIN_FRAMES 5 #define EXTRACTOR_MAX_PROBE_PACKETS 200 - #define FF_MAX_EXTRADATA_SIZE ((1 << 28) - FF_INPUT_BUFFER_PADDING_SIZE) +//debug +#define DEBUG_READ_ENTRY 0 +#define DEBUG_DISABLE_VIDEO 0 +#define DEBUG_DISABLE_AUDIO 0 +#define WAIT_KEY_PACKET_AFTER_SEEK 1 +#define DISABLE_NAL_TO_ANNEXB 0 +#define DEBUG_PKT 0 + enum { NO_SEEK = 0, SEEK, @@ -62,8 +67,10 @@ static AVPacket flush_pkt; namespace android { +static const char *findMatchingContainer(const char *name); + struct FFmpegExtractor::Track : public MediaSource { - Track(const sp &extractor, sp meta, bool isAVC, + Track(FFmpegExtractor *extractor, sp meta, bool isAVC, AVStream *stream, PacketQueue *queue); virtual status_t start(MetaData *params); @@ -79,7 +86,7 @@ protected: private: friend struct FFmpegExtractor; - sp mExtractor; + FFmpegExtractor *mExtractor; sp mMeta; enum AVMediaType mMediaType; @@ -93,6 +100,8 @@ private: AVStream *mStream; PacketQueue *mQueue; + int64_t mFirstKeyPktTimestamp; + DISALLOW_EVIL_CONSTRUCTORS(Track); }; @@ -100,27 +109,18 @@ private: FFmpegExtractor::FFmpegExtractor(const sp &source) : mDataSource(source), - mReaderThreadStarted(false), - mInitCheck(NO_INIT) { - LOGV("FFmpegExtractor::FFmpegExtractor"); + mMeta(new MetaData), + mInitCheck(NO_INIT), + mFFmpegInited(false), + mFormatCtx(NULL), + mReaderThreadStarted(false) { + ALOGV("FFmpegExtractor::FFmpegExtractor"); - int err; - const char *url = mDataSource->getNamURI(); - if (url == NULL) { - LOGI("url is error!"); - return; - } - // is it right? - if (!strcmp(url, "-")) { - av_strlcpy(mFilename, "pipe:", strlen("pipe:") + 1); - } else { - av_strlcpy(mFilename, url, strlen(url) + 1); - } - LOGI("url: %s, mFilename: %s", url, mFilename); + buildFileName(source); - err = initStreams(); + int err = initStreams(); if (err < 0) { - LOGE("failed to init ffmpeg"); + ALOGE("failed to init ffmpeg"); return; } @@ -131,17 +131,17 @@ FFmpegExtractor::FFmpegExtractor(const sp &source) (mFormatCtx->pb ? !mFormatCtx->pb->error : 1) && (mDefersToCreateVideoTrack || mDefersToCreateAudioTrack)) { // FIXME, i am so lazy! Should use pthread_cond_wait to wait conditions - SDL_Delay(5); + usleep(5000); } - LOGV("mProbePkts: %d, mEOF: %d, pb->error(if has): %d, mDefersToCreateVideoTrack: %d, mDefersToCreateAudioTrack: %d", + ALOGV("mProbePkts: %d, mEOF: %d, pb->error(if has): %d, mDefersToCreateVideoTrack: %d, mDefersToCreateAudioTrack: %d", mProbePkts, mEOF, mFormatCtx->pb ? mFormatCtx->pb->error : 0, mDefersToCreateVideoTrack, mDefersToCreateAudioTrack); mInitCheck = OK; } FFmpegExtractor::~FFmpegExtractor() { - LOGV("FFmpegExtractor::~FFmpegExtractor"); + ALOGV("FFmpegExtractor::~FFmpegExtractor"); // stop reader here if no track! stopReaderThread(); @@ -154,7 +154,7 @@ size_t FFmpegExtractor::countTracks() { } sp FFmpegExtractor::getTrack(size_t index) { - LOGV("FFmpegExtractor::getTrack[%d]", index); + ALOGV("FFmpegExtractor::getTrack[%d]", index); if (mInitCheck != OK) { return NULL; @@ -168,7 +168,7 @@ sp FFmpegExtractor::getTrack(size_t index) { } sp FFmpegExtractor::getTrackMetaData(size_t index, uint32_t flags) { - LOGV("FFmpegExtractor::getTrackMetaData[%d]", index); + ALOGV("FFmpegExtractor::getTrackMetaData[%d]", index); if (mInitCheck != OK) { return NULL; @@ -182,24 +182,20 @@ sp FFmpegExtractor::getTrackMetaData(size_t index, uint32_t flags) { } sp FFmpegExtractor::getMetaData() { - LOGV("FFmpegExtractor::getMetaData"); + ALOGV("FFmpegExtractor::getMetaData"); if (mInitCheck != OK) { return NULL; } - sp meta = new MetaData; - // TODO - meta->setCString(kKeyMIMEType, "video/ffmpeg"); - - return meta; + return mMeta; } uint32_t FFmpegExtractor::flags() const { - LOGV("FFmpegExtractor::flags"); + ALOGV("FFmpegExtractor::flags"); if (mInitCheck != OK) { - return NULL; + return 0; } uint32_t flags = CAN_PAUSE; @@ -214,8 +210,8 @@ uint32_t FFmpegExtractor::flags() const { void FFmpegExtractor::packet_queue_init(PacketQueue *q) { memset(q, 0, sizeof(PacketQueue)); - q->mutex = SDL_CreateMutex(); - q->cond = SDL_CreateCond(); + pthread_mutex_init(&q->mutex, NULL); + pthread_cond_init(&q->cond, NULL); packet_queue_put(q, &flush_pkt); } @@ -223,7 +219,7 @@ void FFmpegExtractor::packet_queue_flush(PacketQueue *q) { AVPacketList *pkt, *pkt1; - SDL_LockMutex(q->mutex); + pthread_mutex_lock(&q->mutex); for (pkt = q->first_pkt; pkt != NULL; pkt = pkt1) { pkt1 = pkt->next; av_free_packet(&pkt->pkt); @@ -233,25 +229,23 @@ void FFmpegExtractor::packet_queue_flush(PacketQueue *q) q->first_pkt = NULL; q->nb_packets = 0; q->size = 0; - SDL_UnlockMutex(q->mutex); + pthread_mutex_unlock(&q->mutex); } void FFmpegExtractor::packet_queue_end(PacketQueue *q) { packet_queue_flush(q); - SDL_DestroyMutex(q->mutex); - SDL_DestroyCond(q->cond); } void FFmpegExtractor::packet_queue_abort(PacketQueue *q) { - SDL_LockMutex(q->mutex); + pthread_mutex_lock(&q->mutex); q->abort_request = 1; - SDL_CondSignal(q->cond); + pthread_cond_signal(&q->cond); - SDL_UnlockMutex(q->mutex); + pthread_mutex_unlock(&q->mutex); } int FFmpegExtractor::packet_queue_put(PacketQueue *q, AVPacket *pkt) @@ -268,7 +262,7 @@ int FFmpegExtractor::packet_queue_put(PacketQueue *q, AVPacket *pkt) pkt1->pkt = *pkt; pkt1->next = NULL; - SDL_LockMutex(q->mutex); + pthread_mutex_lock(&q->mutex); if (!q->last_pkt) @@ -279,9 +273,9 @@ int FFmpegExtractor::packet_queue_put(PacketQueue *q, AVPacket *pkt) q->nb_packets++; //q->size += pkt1->pkt.size + sizeof(*pkt1); q->size += pkt1->pkt.size; - SDL_CondSignal(q->cond); + pthread_cond_signal(&q->cond); - SDL_UnlockMutex(q->mutex); + pthread_mutex_unlock(&q->mutex); return 0; } @@ -292,7 +286,7 @@ int FFmpegExtractor::packet_queue_get(PacketQueue *q, AVPacket *pkt, int block) AVPacketList *pkt1; int ret; - SDL_LockMutex(q->mutex); + pthread_mutex_lock(&q->mutex); for (;;) { if (q->abort_request) { @@ -316,32 +310,13 @@ int FFmpegExtractor::packet_queue_get(PacketQueue *q, AVPacket *pkt, int block) ret = 0; break; } else { - SDL_CondWait(q->cond, q->mutex); + pthread_cond_wait(&q->cond, &q->mutex); } } - SDL_UnlockMutex(q->mutex); + pthread_mutex_unlock(&q->mutex); return ret; } -static int lockmgr(void **mtx, enum AVLockOp op) -{ - switch(op) { - case AV_LOCK_CREATE: - *mtx = (void *)SDL_CreateMutex(); - if(!*mtx) - return 1; - return 0; - case AV_LOCK_OBTAIN: - return !!SDL_LockMutex((SDL_mutex *)*mtx); - case AV_LOCK_RELEASE: - return !!SDL_UnlockMutex((SDL_mutex *)*mtx); - case AV_LOCK_DESTROY: - SDL_DestroyMutex((SDL_mutex *)*mtx); - return 0; - } - return 1; -} - static void EncodeSize14(uint8_t **_ptr, size_t size) { CHECK_LE(size, 0x3fff); @@ -400,9 +375,10 @@ static uint32_t get_sample_rate(const uint8_t sf_index) int FFmpegExtractor::check_extradata(AVCodecContext *avctx) { - const char *name; - bool *defersToCreateTrack; - AVBitStreamFilterContext **bsfc; + enum AVCodecID codec_id = AV_CODEC_ID_NONE; + const char *name = NULL; + bool *defersToCreateTrack = NULL; + AVBitStreamFilterContext **bsfc = NULL; // init if (avctx->codec_type == AVMEDIA_TYPE_VIDEO) { @@ -413,21 +389,22 @@ int FFmpegExtractor::check_extradata(AVCodecContext *avctx) defersToCreateTrack = &mDefersToCreateAudioTrack; } + codec_id = avctx->codec_id; + // ignore extradata - if (avctx->codec_id == CODEC_ID_MP3 || - avctx->codec_id == CODEC_ID_MP1 || - avctx->codec_id == CODEC_ID_MP2 || - avctx->codec_id == CODEC_ID_AC3 || - avctx->codec_id == CODEC_ID_H263 || - avctx->codec_id == CODEC_ID_H263P || - avctx->codec_id == CODEC_ID_H263I) + if (codec_id != AV_CODEC_ID_H264 + && codec_id != AV_CODEC_ID_MPEG4 + && codec_id != AV_CODEC_ID_MPEG1VIDEO + && codec_id != AV_CODEC_ID_MPEG2VIDEO + && codec_id != AV_CODEC_ID_AAC) { return 1; + } // is extradata compatible with android? - if (avctx->codec_id != CODEC_ID_AAC) { + if (codec_id != AV_CODEC_ID_AAC) { int is_compatible = is_extradata_compatible_with_android(avctx); if (!is_compatible) { - LOGI("%s extradata is not compatible with android, should to extract it from bitstream", + ALOGI("%s extradata is not compatible with android, should to extract it from bitstream", av_get_media_type_string(avctx->codec_type)); *defersToCreateTrack = true; *bsfc = NULL; // H264 don't need bsfc, only AAC? @@ -436,23 +413,23 @@ int FFmpegExtractor::check_extradata(AVCodecContext *avctx) return 1; } - if (avctx->codec_id == CODEC_ID_AAC) { + if (codec_id == AV_CODEC_ID_AAC) { name = "aac_adtstoasc"; } if (avctx->extradata_size <= 0) { - LOGI("No %s extradata found, should to extract it from bitstream", + ALOGI("No %s extradata found, should to extract it from bitstream", av_get_media_type_string(avctx->codec_type)); *defersToCreateTrack = true; //CHECK(name != NULL); if (!*bsfc && name) { *bsfc = av_bitstream_filter_init(name); if (!*bsfc) { - LOGE("Cannot open the %s BSF!", name); + ALOGE("Cannot open the %s BSF!", name); *defersToCreateTrack = false; return -1; } else { - LOGV("open the %s bsf", name); + ALOGV("open the %s bsf", name); return 0; } } else { @@ -462,56 +439,101 @@ int FFmpegExtractor::check_extradata(AVCodecContext *avctx) return 1; } +void FFmpegExtractor::printTime(int64_t time) +{ + int hours, mins, secs, us; -int FFmpegExtractor::stream_component_open(int stream_index) + if (time == AV_NOPTS_VALUE) + return; + + secs = time / AV_TIME_BASE; + us = time % AV_TIME_BASE; + mins = secs / 60; + secs %= 60; + hours = mins / 60; + mins %= 60; + ALOGI("the time is %02d:%02d:%02d.%02d", + hours, mins, secs, (100 * us) / AV_TIME_BASE); +} + +bool FFmpegExtractor::is_codec_supported(enum AVCodecID codec_id) { - AVCodecContext *avctx; - sp meta; - bool isAVC = false; bool supported = false; - uint32_t type; - const void *data; - size_t size; - int ret; - LOGI("stream_index: %d", stream_index); - if (stream_index < 0 || stream_index >= mFormatCtx->nb_streams) - return -1; - avctx = mFormatCtx->streams[stream_index]->codec; + switch(codec_id) { + case AV_CODEC_ID_H264: + case AV_CODEC_ID_MPEG4: + case AV_CODEC_ID_H263: + case AV_CODEC_ID_H263P: + case AV_CODEC_ID_H263I: + case AV_CODEC_ID_AAC: + case AV_CODEC_ID_AC3: + case AV_CODEC_ID_MP2: + case AV_CODEC_ID_MP3: + case AV_CODEC_ID_MPEG1VIDEO: + case AV_CODEC_ID_MPEG2VIDEO: + case AV_CODEC_ID_WMV1: + case AV_CODEC_ID_WMV2: + case AV_CODEC_ID_WMV3: + case AV_CODEC_ID_VC1: + case AV_CODEC_ID_WMAV1: + case AV_CODEC_ID_WMAV2: + case AV_CODEC_ID_WMAPRO: + case AV_CODEC_ID_WMALOSSLESS: + case AV_CODEC_ID_RV20: + case AV_CODEC_ID_RV30: + case AV_CODEC_ID_RV40: + case AV_CODEC_ID_COOK: + case AV_CODEC_ID_APE: + case AV_CODEC_ID_DTS: + case AV_CODEC_ID_FLAC: + case AV_CODEC_ID_FLV1: + case AV_CODEC_ID_VORBIS: + case AV_CODEC_ID_HEVC: - switch(avctx->codec_id) { - case CODEC_ID_H264: - case CODEC_ID_MPEG4: - case CODEC_ID_H263: - case CODEC_ID_H263P: - case CODEC_ID_H263I: - case CODEC_ID_AAC: - case CODEC_ID_AC3: - case CODEC_ID_MP1: - case CODEC_ID_MP2: - case CODEC_ID_MP3: - case CODEC_ID_MPEG2VIDEO: -#if 0 - case CODEC_ID_VC1: -#endif supported = true; break; default: - supported = false; + ALOGD("unsuppoted codec(%s), but give it a chance", + avcodec_get_name(codec_id)); + //Won't promise that the following codec id can be supported. + //Just give these codecs a chance. + supported = true; break; } + return supported; +} + +int FFmpegExtractor::stream_component_open(int stream_index) +{ + AVCodecContext *avctx = NULL; + sp meta = NULL; + bool isAVC = false; + bool supported = false; + uint32_t type = 0; + const void *data = NULL; + size_t size = 0; + int ret = 0; + + ALOGI("stream_index: %d", stream_index); + if (stream_index < 0 || stream_index >= (int)mFormatCtx->nb_streams) + return -1; + avctx = mFormatCtx->streams[stream_index]->codec; + + supported = is_codec_supported(avctx->codec_id); + if (!supported) { - LOGE("unsupport the codec, id: 0x%0x", avctx->codec_id); + ALOGE("unsupport the codec(%s)", avcodec_get_name(avctx->codec_id)); return -1; } - LOGV("support the codec"); + ALOGI("support the codec(%s)", avcodec_get_name(avctx->codec_id)); unsigned streamType; ssize_t index = mTracks.indexOfKey(stream_index); if (index >= 0) { - LOGE("this track already exists"); + ALOGE("this track already exists"); return 0; } @@ -519,7 +541,7 @@ int FFmpegExtractor::stream_component_open(int stream_index) char tagbuf[32]; av_get_codec_tag_string(tagbuf, sizeof(tagbuf), avctx->codec_tag); - LOGV("Tag %s/0x%08x with codec id '%d'\n", tagbuf, avctx->codec_tag, avctx->codec_id); + ALOGV("Tag %s/0x%08x with codec(%s)\n", tagbuf, avctx->codec_tag, avcodec_get_name(avctx->codec_id)); switch (avctx->codec_type) { case AVMEDIA_TYPE_VIDEO: @@ -528,7 +550,7 @@ int FFmpegExtractor::stream_component_open(int stream_index) if (mVideoStream == NULL) mVideoStream = mFormatCtx->streams[stream_index]; if (!mVideoQInited) { - packet_queue_init(&mVideoQ); + packet_queue_init(&mVideoQ); mVideoQInited = true; } @@ -546,16 +568,16 @@ int FFmpegExtractor::stream_component_open(int stream_index) } if (avctx->extradata) { - LOGV("video stream extradata:"); + ALOGV("video stream extradata:"); hexdump(avctx->extradata, avctx->extradata_size); } else { - LOGV("video stream no extradata, but we can ignore it."); + ALOGV("video stream no extradata, but we can ignore it."); } meta = new MetaData; switch(avctx->codec_id) { - case CODEC_ID_H264: + case AV_CODEC_ID_H264: /** * H.264 Video Types * http://msdn.microsoft.com/en-us/library/dd757808(v=vs.85).aspx @@ -564,7 +586,7 @@ int FFmpegExtractor::stream_component_open(int stream_index) if (avctx->extradata[0] == 1 /* configurationVersion */) { // H.264 bitstream without start codes. isAVC = true; - LOGV("AVC"); + ALOGV("AVC"); if (avctx->width == 0 || avctx->height == 0) { int32_t width, height; @@ -580,7 +602,7 @@ int FFmpegExtractor::stream_component_open(int stream_index) } else { // H.264 bitstream with start codes. isAVC = false; - LOGV("H264"); + ALOGV("H264"); /* set NULL to release meta as we will new a meta in MakeAVCCodecSpecificData() fxn */ meta->clear(); @@ -591,8 +613,8 @@ int FFmpegExtractor::stream_component_open(int stream_index) meta = MakeAVCCodecSpecificData(buffer); } break; - case CODEC_ID_MPEG4: - LOGV("MPEG4"); + case AV_CODEC_ID_MPEG4: + ALOGV("MPEG4"); meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_MPEG4); { sp csd = new ABuffer(avctx->extradata_size); @@ -601,14 +623,15 @@ int FFmpegExtractor::stream_component_open(int stream_index) meta->setData(kKeyESDS, kTypeESDS, esds->data(), esds->size()); } break; - case CODEC_ID_H263: - case CODEC_ID_H263P: - case CODEC_ID_H263I: - LOGV("H263"); + case AV_CODEC_ID_H263: + case AV_CODEC_ID_H263P: + case AV_CODEC_ID_H263I: + ALOGV("H263"); meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_H263); break; - case CODEC_ID_MPEG2VIDEO: - LOGV("MPEG2VIDEO"); + case AV_CODEC_ID_MPEG1VIDEO: + case AV_CODEC_ID_MPEG2VIDEO: + ALOGV("MPEG%dVIDEO", avctx->codec_id == AV_CODEC_ID_MPEG2VIDEO ? 2 : 1); meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_MPEG2); { sp csd = new ABuffer(avctx->extradata_size); @@ -617,26 +640,91 @@ int FFmpegExtractor::stream_component_open(int stream_index) meta->setData(kKeyESDS, kTypeESDS, esds->data(), esds->size()); } break; - case CODEC_ID_VC1: - LOGV("VC1"); - meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_WMV12); - meta->setData(kKeyESDS, kTypeESDS, avctx->extradata, avctx->extradata_size); + case AV_CODEC_ID_VC1: + ALOGV("VC1"); + meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_VC1); + meta->setData(kKeyRawCodecSpecificData, 0, avctx->extradata, avctx->extradata_size); + break; + case AV_CODEC_ID_WMV1: + ALOGV("WMV1"); + meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_WMV); + meta->setInt32(kKeyWMVVersion, kTypeWMVVer_7); + break; + case AV_CODEC_ID_WMV2: + ALOGV("WMV2"); + meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_WMV); + meta->setData(kKeyRawCodecSpecificData, 0, avctx->extradata, avctx->extradata_size); + meta->setInt32(kKeyWMVVersion, kTypeWMVVer_8); + break; + case AV_CODEC_ID_WMV3: + ALOGV("WMV3"); + meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_WMV); + meta->setData(kKeyRawCodecSpecificData, 0, avctx->extradata, avctx->extradata_size); + meta->setInt32(kKeyWMVVersion, kTypeWMVVer_9); + break; + case AV_CODEC_ID_RV20: + ALOGV("RV30"); + meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_RV); + meta->setData(kKeyRawCodecSpecificData, 0, avctx->extradata, avctx->extradata_size); + meta->setInt32(kKeyRVVersion, kTypeRVVer_G2); //http://en.wikipedia.org/wiki/RealVideo + case AV_CODEC_ID_RV30: + ALOGV("RV30"); + meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_RV); + meta->setData(kKeyRawCodecSpecificData, 0, avctx->extradata, avctx->extradata_size); + meta->setInt32(kKeyRVVersion, kTypeRVVer_8); //http://en.wikipedia.org/wiki/RealVideo + break; + case AV_CODEC_ID_RV40: + ALOGV("RV40"); + meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_RV); + meta->setData(kKeyRawCodecSpecificData, 0, avctx->extradata, avctx->extradata_size); + meta->setInt32(kKeyRVVersion, kTypeRVVer_9); //http://en.wikipedia.org/wiki/RealVideo + break; + case AV_CODEC_ID_FLV1: + ALOGV("FLV1"); + meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_FLV1); + meta->setData(kKeyRawCodecSpecificData, 0, avctx->extradata, avctx->extradata_size); + break; + case AV_CODEC_ID_HEVC: + ALOGV("HEVC"); + meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_HEVC); + meta->setData(kKeyRawCodecSpecificData, 0, avctx->extradata, avctx->extradata_size); break; default: - CHECK(!"Should not be here. Unsupported codec."); + ALOGD("unsuppoted video codec(id:%d, name:%s), but give it a chance", + avctx->codec_id, avcodec_get_name(avctx->codec_id)); + meta = new MetaData; + meta->setInt32(kKeyCodecId, avctx->codec_id); + meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_FFMPEG); + if (avctx->extradata_size > 0) { + meta->setData(kKeyRawCodecSpecificData, 0, avctx->extradata, avctx->extradata_size); + } + //CHECK(!"Should not be here. Unsupported codec."); break; } - LOGI("width: %d, height: %d, bit_rate: %d", avctx->width, avctx->height, avctx->bit_rate); + ALOGI("width: %d, height: %d, bit_rate: %d", + avctx->width, avctx->height, avctx->bit_rate); meta->setInt32(kKeyWidth, avctx->width); meta->setInt32(kKeyHeight, avctx->height); if (avctx->bit_rate > 0) meta->setInt32(kKeyBitRate, avctx->bit_rate); - if (mFormatCtx->duration != AV_NOPTS_VALUE) + if (mVideoStream->duration != AV_NOPTS_VALUE) { + int64_t duration = mVideoStream->duration * av_q2d(mVideoStream->time_base) * 1000000; + printTime(duration); + ALOGV("video startTime: %lld", mVideoStream->start_time); + if (mVideoStream->start_time != AV_NOPTS_VALUE) { + ALOGV("video startTime:%lld", mVideoStream->start_time); + } else { + ALOGV("video startTime:N/A"); + } + meta->setInt64(kKeyDuration, duration); + } else { + // default when no stream duration meta->setInt64(kKeyDuration, mFormatCtx->duration); + } - LOGV("create a video track"); + ALOGV("create a video track"); index = mTracks.add( stream_index, new Track(this, meta, isAVC, mVideoStream, &mVideoQ)); @@ -667,35 +755,36 @@ int FFmpegExtractor::stream_component_open(int stream_index) } if (avctx->extradata) { - LOGV("audio stream extradata:"); + ALOGV("audio stream extradata(%d):", avctx->extradata_size); hexdump(avctx->extradata, avctx->extradata_size); } else { - LOGV("audio stream no extradata, but we can ignore it."); + ALOGV("audio stream no extradata, but we can ignore it."); } switch(avctx->codec_id) { - case CODEC_ID_MP1: - LOGV("MP1"); - meta = new MetaData; - meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MPEG_LAYER_I); - break; - case CODEC_ID_MP2: - LOGV("MP2"); + case AV_CODEC_ID_MP2: + ALOGV("MP2"); meta = new MetaData; meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MPEG_LAYER_II); break; - case CODEC_ID_MP3: - LOGV("MP3"); + case AV_CODEC_ID_MP3: + ALOGV("MP3"); meta = new MetaData; meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MPEG); break; - case CODEC_ID_AC3: - LOGV("AC3"); + case AV_CODEC_ID_VORBIS: + ALOGV("VORBIS"); + meta = new MetaData; + meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_VORBIS); + meta->setData(kKeyRawCodecSpecificData, 0, avctx->extradata, avctx->extradata_size); + break; + case AV_CODEC_ID_AC3: + ALOGV("AC3"); meta = new MetaData; meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AC3); break; - case CODEC_ID_AAC: - LOGV("AAC"); + case AV_CODEC_ID_AAC: + ALOGV("AAC"); uint32_t sr; const uint8_t *header; uint8_t profile, sf_index, channel; @@ -712,38 +801,105 @@ int FFmpegExtractor::stream_component_open(int stream_index) sf_index = (header[0] & 0x07) << 1 | (header[1] & 0x80) >> 7; sr = get_sample_rate(sf_index); if (sr == 0) { - LOGE("unsupport the sample rate"); + ALOGE("unsupport the sample rate"); return -1; } channel = (header[1] >> 3) & 0xf; - LOGV("profile: %d, sf_index: %d, channel: %d", profile, sf_index, channel); + ALOGV("profile: %d, sf_index: %d, channel: %d", profile, sf_index, channel); meta = MakeAACCodecSpecificData(profile, sf_index, channel); meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AAC); break; + case AV_CODEC_ID_WMAV1: // TODO, version? + ALOGV("WMAV1"); + meta = new MetaData; + meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_WMA); + meta->setData(kKeyRawCodecSpecificData, 0, avctx->extradata, avctx->extradata_size); + break; + case AV_CODEC_ID_WMAV2: + ALOGV("WMAV2"); + meta = new MetaData; + meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_WMA); + meta->setData(kKeyRawCodecSpecificData, 0, avctx->extradata, avctx->extradata_size); + meta->setInt32(kKeyWMAVersion, kTypeWMA); + break; + case AV_CODEC_ID_WMAPRO: + ALOGV("WMAPRO"); + meta = new MetaData; + meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_WMA); + meta->setData(kKeyRawCodecSpecificData, 0, avctx->extradata, avctx->extradata_size); + meta->setInt32(kKeyWMAVersion, kTypeWMAPro); + break; + case AV_CODEC_ID_WMALOSSLESS: + ALOGV("WMALOSSLESS"); + meta = new MetaData; + meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_WMA); + meta->setData(kKeyRawCodecSpecificData, 0, avctx->extradata, avctx->extradata_size); + meta->setInt32(kKeyWMAVersion, kTypeWMALossLess); + break; + case AV_CODEC_ID_COOK: // audio codec in RMVB + ALOGV("COOK"); + meta = new MetaData; + meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_RA); + meta->setData(kKeyRawCodecSpecificData, 0, avctx->extradata, avctx->extradata_size); + break; + case AV_CODEC_ID_APE: + ALOGV("APE"); + meta = new MetaData; + meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_APE); + meta->setData(kKeyRawCodecSpecificData, 0, avctx->extradata, avctx->extradata_size); + break; + case AV_CODEC_ID_DTS: + ALOGV("DTS"); + meta = new MetaData; + meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_DTS); + meta->setData(kKeyRawCodecSpecificData, 0, avctx->extradata, avctx->extradata_size); + break; + case AV_CODEC_ID_FLAC: + ALOGV("FLAC"); + meta = new MetaData; + meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_FLAC); + meta->setData(kKeyRawCodecSpecificData, 0, avctx->extradata, avctx->extradata_size); + break; default: - CHECK(!"Should not be here. Unsupported codec."); + ALOGD("unsuppoted audio codec(id:%d, name:%s), but give it a chance", + avctx->codec_id, avcodec_get_name(avctx->codec_id)); + meta = new MetaData; + meta->setInt32(kKeyCodecId, avctx->codec_id); + meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_FFMPEG); + if (avctx->extradata_size > 0) { + meta->setData(kKeyRawCodecSpecificData, 0, avctx->extradata, avctx->extradata_size); + } + //CHECK(!"Should not be here. Unsupported codec."); break; } - LOGI("bit_rate: %d, sample_rate: %d, channels: %d", avctx->bit_rate, avctx->sample_rate, avctx->channels); + ALOGI("bit_rate: %d, sample_rate: %d, channels: %d, " + "bits_per_coded_sample: %d, block_align:%d", + avctx->bit_rate, avctx->sample_rate, avctx->channels, + avctx->bits_per_coded_sample, avctx->block_align); - meta->setInt32(kKeySampleRate, avctx->sample_rate); meta->setInt32(kKeyChannelCount, avctx->channels); meta->setInt32(kKeyBitRate, avctx->bit_rate); - if (mFormatCtx->duration != AV_NOPTS_VALUE) + meta->setInt32(kKeyBitspersample, avctx->bits_per_coded_sample); + meta->setInt32(kKeySampleRate, avctx->sample_rate); + meta->setInt32(kKeyBlockAlign, avctx->block_align); + meta->setInt32(kKeySampleFormat, avctx->sample_fmt); + if (mAudioStream->duration != AV_NOPTS_VALUE) { + int64_t duration = mAudioStream->duration * av_q2d(mAudioStream->time_base) * 1000000; + printTime(duration); + if (mAudioStream->start_time != AV_NOPTS_VALUE) { + ALOGV("audio startTime:%lld", mAudioStream->start_time); + } else { + ALOGV("audio startTime:N/A"); + } + meta->setInt64(kKeyDuration, duration); + } else { + // default when no stream duration meta->setInt64(kKeyDuration, mFormatCtx->duration); - - if (avctx->codec_id != CODEC_ID_MP3 && - avctx->codec_id != CODEC_ID_MP1 && - avctx->codec_id != CODEC_ID_MP2 && - avctx->codec_id != CODEC_ID_AC3) { - LOGV("audio meta esds:"); - CHECK(meta->findData(kKeyESDS, &type, &data, &size)); - hexdump(data, size); } - LOGV("create a audio track"); + ALOGV("create a audio track"); index = mTracks.add( stream_index, new Track(this, meta, false, mAudioStream, &mAudioQ)); @@ -765,30 +921,30 @@ void FFmpegExtractor::stream_component_close(int stream_index) { AVCodecContext *avctx; - if (stream_index < 0 || stream_index >= mFormatCtx->nb_streams) + if (stream_index < 0 || stream_index >= (int)mFormatCtx->nb_streams) return; avctx = mFormatCtx->streams[stream_index]->codec; switch (avctx->codec_type) { case AVMEDIA_TYPE_VIDEO: - LOGV("packet_queue_abort videoq"); + ALOGV("packet_queue_abort videoq"); packet_queue_abort(&mVideoQ); /* wait until the end */ while (!mAbortRequest && !mVideoEOSReceived) { - LOGV("wait for video received"); - SDL_Delay(10); + ALOGV("wait for video received"); + usleep(10000); } - LOGV("packet_queue_end videoq"); + ALOGV("packet_queue_end videoq"); packet_queue_end(&mVideoQ); break; case AVMEDIA_TYPE_AUDIO: - LOGV("packet_queue_abort audioq"); + ALOGV("packet_queue_abort audioq"); packet_queue_abort(&mAudioQ); while (!mAbortRequest && !mAudioEOSReceived) { - LOGV("wait for audio received"); - SDL_Delay(10); + ALOGV("wait for audio received"); + usleep(10000); } - LOGV("packet_queue_end audioq"); + ALOGV("packet_queue_end audioq"); packet_queue_end(&mAudioQ); break; case AVMEDIA_TYPE_SUBTITLE: @@ -865,32 +1021,44 @@ int FFmpegExtractor::decode_interrupt_cb(void *ctx) return extrator->mAbortRequest; } -void FFmpegExtractor::print_error_ex(const char *filename, int err) +void FFmpegExtractor::buildFileName(const sp &source) { - char errbuf[128]; - const char *errbuf_ptr = errbuf; - - if (av_strerror(err, errbuf, sizeof(errbuf)) < 0) - errbuf_ptr = strerror(AVUNERROR(err)); - LOGI("%s: %s\n", filename, errbuf_ptr); +#if 1 + ALOGI("android-source:%p", source.get()); + // pass the addr of smart pointer("source") + snprintf(mFilename, sizeof(mFilename), "android-source:%p", source.get()); + ALOGI("build mFilename: %s", mFilename); +#else + const char *url = mDataSource->getNamURI(); + if (url == NULL) { + ALOGI("url is error!"); + return; + } + // is it right? + if (!strcmp(url, "-")) { + av_strlcpy(mFilename, "pipe:", strlen("pipe:") + 1); + } else { + av_strlcpy(mFilename, url, strlen(url) + 1); + } + ALOGI("build url: %s, mFilename: %s", url, mFilename); +#endif } void FFmpegExtractor::setFFmpegDefaultOpts() { mGenPTS = 0; -#if DIABLE_VIDEO +#if DEBUG_DISABLE_VIDEO mVideoDisable = 1; #else mVideoDisable = 0; #endif -#if DIABLE_AUDIO +#if DEBUG_DISABLE_AUDIO mAudioDisable = 1; #else mAudioDisable = 0; #endif - mShowStatus = 1; + mShowStatus = 0; mSeekByBytes = 0; /* seek by bytes 0=off 1=on -1=auto" */ - mStartTime = AV_NOPTS_VALUE; mDuration = AV_NOPTS_VALUE; mSeekPos = AV_NOPTS_VALUE; mAutoExit = 1; @@ -918,20 +1086,22 @@ void FFmpegExtractor::setFFmpegDefaultOpts() int FFmpegExtractor::initStreams() { - int err, i; - status_t status; + int err = 0; + int i = 0; + status_t status = UNKNOWN_ERROR; int eof = 0; - int ret = 0, audio_ret = 0, video_ret = 0; + int ret = 0, audio_ret = -1, video_ret = -1; int pkt_in_play_range = 0; - AVDictionaryEntry *t; - AVDictionary **opts; - int orig_nb_streams; + AVDictionaryEntry *t = NULL; + AVDictionary **opts = NULL; + int orig_nb_streams = 0; int st_index[AVMEDIA_TYPE_NB] = {0}; int wanted_stream[AVMEDIA_TYPE_NB] = {0}; st_index[AVMEDIA_TYPE_AUDIO] = -1; st_index[AVMEDIA_TYPE_VIDEO] = -1; wanted_stream[AVMEDIA_TYPE_AUDIO] = -1; wanted_stream[AVMEDIA_TYPE_VIDEO] = -1; + const char *mime = NULL; setFFmpegDefaultOpts(); @@ -940,23 +1110,30 @@ int FFmpegExtractor::initStreams() ret = -1; goto fail; } + mFFmpegInited = true; av_init_packet(&flush_pkt); flush_pkt.data = (uint8_t *)"FLUSH"; flush_pkt.size = 0; mFormatCtx = avformat_alloc_context(); + if (!mFormatCtx) + { + ALOGE("oom for alloc avformat context"); + ret = -1; + goto fail; + } mFormatCtx->interrupt_callback.callback = decode_interrupt_cb; mFormatCtx->interrupt_callback.opaque = this; - LOGV("mFilename: %s", mFilename); + ALOGV("mFilename: %s", mFilename); err = avformat_open_input(&mFormatCtx, mFilename, NULL, &format_opts); if (err < 0) { - print_error_ex(mFilename, err); + ALOGE("%s: avformat_open_input failed, err:%s", mFilename, av_err2str(err)); ret = -1; goto fail; } if ((t = av_dict_get(format_opts, "", NULL, AV_DICT_IGNORE_SUFFIX))) { - LOGE("Option %s not found.\n", t->key); + ALOGE("Option %s not found.\n", t->key); //ret = AVERROR_OPTION_NOT_FOUND; ret = -1; goto fail; @@ -970,7 +1147,7 @@ int FFmpegExtractor::initStreams() err = avformat_find_stream_info(mFormatCtx, opts); if (err < 0) { - LOGE("%s: could not find codec parameters\n", mFilename); + ALOGE("%s: could not find stream info, err:%s", mFilename, av_err2str(err)); ret = -1; goto fail; } @@ -978,29 +1155,17 @@ int FFmpegExtractor::initStreams() av_dict_free(&opts[i]); av_freep(&opts); + mime = findMatchingContainer(mFormatCtx->iformat->name); + CHECK(mime != NULL); + mMeta->setCString(kKeyMIMEType, mime); + if (mFormatCtx->pb) mFormatCtx->pb->eof_reached = 0; // FIXME hack, ffplay maybe should not use url_feof() to test for the end if (mSeekByBytes < 0) mSeekByBytes = !!(mFormatCtx->iformat->flags & AVFMT_TS_DISCONT); - /* if seeking requested, we execute it */ - if (mStartTime != AV_NOPTS_VALUE) { - int64_t timestamp; - - timestamp = mStartTime; - /* add the stream start time */ - if (mFormatCtx->start_time != AV_NOPTS_VALUE) - timestamp += mFormatCtx->start_time; - ret = avformat_seek_file(mFormatCtx, -1, INT64_MIN, timestamp, INT64_MAX, 0); - if (ret < 0) { - LOGE("%s: could not seek to position %0.3f", - mFilename, (double)timestamp / AV_TIME_BASE); - goto fail; - } - } - - for (i = 0; i < mFormatCtx->nb_streams; i++) + for (i = 0; i < (int)mFormatCtx->nb_streams; i++) mFormatCtx->streams[i]->discard = AVDISCARD_ALL; if (!mVideoDisable) st_index[AVMEDIA_TYPE_VIDEO] = @@ -1016,15 +1181,22 @@ int FFmpegExtractor::initStreams() av_dump_format(mFormatCtx, 0, mFilename, 0); } - if (mFormatCtx->duration != AV_NOPTS_VALUE) { + if (mFormatCtx->duration != AV_NOPTS_VALUE && + mFormatCtx->start_time != AV_NOPTS_VALUE) { int hours, mins, secs, us; - secs = mFormatCtx->duration / AV_TIME_BASE; - us = mFormatCtx->duration % AV_TIME_BASE; + + ALOGV("file startTime: %lld", mFormatCtx->start_time); + + mDuration = mFormatCtx->duration; + + secs = mDuration / AV_TIME_BASE; + us = mDuration % AV_TIME_BASE; mins = secs / 60; secs %= 60; hours = mins / 60; mins %= 60; - LOGI("the duration is %02d:%02d:%02d.%02d", hours, mins, secs, (100 * us) / AV_TIME_BASE); + ALOGI("the duration is %02d:%02d:%02d.%02d", + hours, mins, secs, (100 * us) / AV_TIME_BASE); } if (st_index[AVMEDIA_TYPE_AUDIO] >= 0) { @@ -1036,7 +1208,7 @@ int FFmpegExtractor::initStreams() } if ( audio_ret < 0 && video_ret < 0) { - LOGE("%s: could not open codecs\n", mFilename); + ALOGE("%s: could not open codecs\n", mFilename); ret = -1; goto fail; } @@ -1053,11 +1225,13 @@ void FFmpegExtractor::deInitStreams() avformat_close_input(&mFormatCtx); } - deInitFFmpeg(); + if (mFFmpegInited) { + deInitFFmpeg(); + } } status_t FFmpegExtractor::startReaderThread() { - LOGV("Starting reader thread"); + ALOGV("Starting reader thread"); Mutex::Autolock autoLock(mLock); if (mReaderThreadStarted) @@ -1069,17 +1243,17 @@ status_t FFmpegExtractor::startReaderThread() { pthread_create(&mReaderThread, &attr, ReaderWrapper, this); pthread_attr_destroy(&attr); mReaderThreadStarted = true; - LOGD("Reader thread started"); + ALOGD("Reader thread started"); return OK; } void FFmpegExtractor::stopReaderThread() { - LOGV("Stopping reader thread"); + ALOGV("Stopping reader thread"); Mutex::Autolock autoLock(mLock); if (!mReaderThreadStarted) { - LOGD("Reader thread have been stopped"); + ALOGD("Reader thread have been stopped"); return; } @@ -1088,7 +1262,7 @@ void FFmpegExtractor::stopReaderThread() { void *dummy; pthread_join(mReaderThread, &dummy); mReaderThreadStarted = false; - LOGD("Reader thread stopped"); + ALOGD("Reader thread stopped"); } // static @@ -1104,7 +1278,7 @@ void FFmpegExtractor::readerEntry() { int eof = 0; int pkt_in_play_range = 0; - LOGV("FFmpegExtractor::readerEntry"); + ALOGV("FFmpegExtractor enter thread(readerEntry)"); mVideoEOSReceived = false; mAudioEOSReceived = false; @@ -1126,16 +1300,16 @@ void FFmpegExtractor::readerEntry() { (mFormatCtx->pb && !strncmp(mFilename, "mmsh:", 5)))) { /* wait 10 ms to avoid trying to get another packet */ /* XXX: horrible */ - SDL_Delay(10); + usleep(10000); continue; } #endif if (mSeekReq) { - LOGV("readerEntry, mSeekReq: %d", mSeekReq); + ALOGV("readerEntry, mSeekReq: %d", mSeekReq); ret = avformat_seek_file(mFormatCtx, -1, INT64_MIN, mSeekPos, INT64_MAX, mSeekFlags); if (ret < 0) { - LOGE("%s: error while seeking", mFormatCtx->filename); + ALOGE("%s: error while seeking", mFormatCtx->filename); } else { if (mAudioStreamIdx >= 0) { packet_queue_flush(&mAudioQ); @@ -1155,10 +1329,10 @@ void FFmpegExtractor::readerEntry() { || ( (mAudioQ .size > MIN_AUDIOQ_SIZE || mAudioStreamIdx < 0) && (mVideoQ .nb_packets > MIN_FRAMES || mVideoStreamIdx < 0))) { #if DEBUG_READ_ENTRY - LOGV("readerEntry, is full, fuck"); + ALOGV("readerEntry, is full, fuck"); #endif /* wait 10 ms */ - SDL_Delay(10); + usleep(10000); continue; } @@ -1177,19 +1351,13 @@ void FFmpegExtractor::readerEntry() { pkt->stream_index = mAudioStreamIdx; packet_queue_put(&mAudioQ, pkt); } - SDL_Delay(10); + usleep(10000); #if DEBUG_READ_ENTRY - LOGV("readerEntry, eof = 1, mVideoQ.size: %d, mVideoQ.nb_packets: %d, mAudioQ.size: %d, mAudioQ.nb_packets: %d", + ALOGV("readerEntry, eof = 1, mVideoQ.size: %d, mVideoQ.nb_packets: %d, mAudioQ.size: %d, mAudioQ.nb_packets: %d", mVideoQ.size, mVideoQ.nb_packets, mAudioQ.size, mAudioQ.nb_packets); #endif if (mAudioQ.size + mVideoQ.size == 0) { - if (mLoop != 1 && (!mLoop || --mLoop)) { - if (mVideoStreamIdx >= 0) { - stream_seek(mStartTime != AV_NOPTS_VALUE ? mStartTime : 0, AVMEDIA_TYPE_VIDEO); - } else if (mAudioStreamIdx >= 0) { - stream_seek(mStartTime != AV_NOPTS_VALUE ? mStartTime : 0, AVMEDIA_TYPE_AUDIO); - } - } else if (mAutoExit) { + if (mAutoExit) { ret = AVERROR_EOF; goto fail; } @@ -1203,19 +1371,19 @@ void FFmpegExtractor::readerEntry() { if (ret < 0) { if (ret == AVERROR_EOF || url_feof(mFormatCtx->pb)) if (ret == AVERROR_EOF) { - //LOGV("ret == AVERROR_EOF"); + //ALOGV("ret == AVERROR_EOF"); } if (url_feof(mFormatCtx->pb)) { - //LOGV("url_feof(mFormatCtx->pb)"); + //ALOGV("url_feof(mFormatCtx->pb)"); } eof = 1; mEOF = true; if (mFormatCtx->pb && mFormatCtx->pb->error) { - LOGE("mFormatCtx->pb->error: %d", mFormatCtx->pb->error); + ALOGE("mFormatCtx->pb->error: %d", mFormatCtx->pb->error); break; } - SDL_Delay(100); + usleep(100000); continue; } @@ -1244,9 +1412,9 @@ void FFmpegExtractor::readerEntry() { stream_component_open(mVideoStreamIdx); if (!mDefersToCreateVideoTrack) - LOGI("probe packet counter: %d when create video track ok", mProbePkts); + ALOGI("probe packet counter: %d when create video track ok", mProbePkts); if (mProbePkts == EXTRACTOR_MAX_PROBE_PACKETS) - LOGI("probe packet counter to max: %d, create video track: %d", + ALOGI("probe packet counter to max: %d, create video track: %d", mProbePkts, !mDefersToCreateVideoTrack); } } else if (pkt->stream_index == mAudioStreamIdx) { @@ -1274,22 +1442,16 @@ void FFmpegExtractor::readerEntry() { } stream_component_open(mAudioStreamIdx); if (!mDefersToCreateAudioTrack) - LOGI("probe packet counter: %d when create audio track ok", mProbePkts); + ALOGI("probe packet counter: %d when create audio track ok", mProbePkts); if (mProbePkts == EXTRACTOR_MAX_PROBE_PACKETS) - LOGI("probe packet counter to max: %d, create audio track: %d", + ALOGI("probe packet counter to max: %d, create audio track: %d", mProbePkts, !mDefersToCreateAudioTrack); } } - /* check if packet is in play range specified by user, then queue, otherwise discard */ - pkt_in_play_range = mDuration == AV_NOPTS_VALUE || - (pkt->pts - mFormatCtx->streams[pkt->stream_index]->start_time) * - av_q2d(mFormatCtx->streams[pkt->stream_index]->time_base) - - (double)(mStartTime != AV_NOPTS_VALUE ? mStartTime : 0) / 1000000 - <= ((double)mDuration / 1000000); - if (pkt->stream_index == mAudioStreamIdx && pkt_in_play_range) { + if (pkt->stream_index == mAudioStreamIdx) { packet_queue_put(&mAudioQ, pkt); - } else if (pkt->stream_index == mVideoStreamIdx && pkt_in_play_range) { + } else if (pkt->stream_index == mVideoStreamIdx) { packet_queue_put(&mVideoQ, pkt); } else { av_free_packet(pkt); @@ -1297,12 +1459,12 @@ void FFmpegExtractor::readerEntry() { } /* wait until the end */ while (!mAbortRequest) { - SDL_Delay(100); + usleep(100000); } ret = 0; fail: - LOGI("reader thread goto end..."); + ALOGI("reader thread goto end..."); /* close each stream */ if (mAudioStreamIdx >= 0) @@ -1312,13 +1474,15 @@ fail: if (mFormatCtx) { avformat_close_input(&mFormatCtx); } + + ALOGV("FFmpegExtractor exit thread(readerEntry)"); } //////////////////////////////////////////////////////////////////////////////// FFmpegExtractor::Track::Track( - const sp &extractor, sp meta, bool isAVC, - AVStream *stream, PacketQueue *queue) + FFmpegExtractor *extractor, sp meta, bool isAVC, + AVStream *stream, PacketQueue *queue) : mExtractor(extractor), mMeta(meta), mIsAVC(isAVC), @@ -1344,27 +1508,36 @@ FFmpegExtractor::Track::Track( // The number of bytes used to encode the length of a NAL unit. mNALLengthSize = 1 + (ptr[4] & 3); - LOGV("the stream is AVC, the length of a NAL unit: %d", mNALLengthSize); + ALOGV("the stream is AVC, the length of a NAL unit: %d", mNALLengthSize); mNal2AnnexB = true; } } mMediaType = mStream->codec->codec_type; + mFirstKeyPktTimestamp = AV_NOPTS_VALUE; } FFmpegExtractor::Track::~Track() { + ALOGV("FFmpegExtractor::Track::~Track %s", + av_get_media_type_string(mMediaType)); + mExtractor = NULL; + mMeta = NULL; } status_t FFmpegExtractor::Track::start(MetaData *params) { + ALOGV("FFmpegExtractor::Track::start %s", + av_get_media_type_string(mMediaType)); Mutex::Autolock autoLock(mLock); //mExtractor->startReaderThread(); return OK; } status_t FFmpegExtractor::Track::stop() { + ALOGV("FFmpegExtractor::Track::stop %s", + av_get_media_type_string(mMediaType)); Mutex::Autolock autoLock(mLock); - mExtractor->stopReaderThread(); + //mExtractor->stopReaderThread(); return OK; } @@ -1386,12 +1559,17 @@ status_t FFmpegExtractor::Track::read( ReadOptions::SeekMode mode; int64_t pktTS = AV_NOPTS_VALUE; int64_t seekTimeUs = AV_NOPTS_VALUE; - int64_t timeUs; - int key; + int64_t timeUs = AV_NOPTS_VALUE; + int key = 0; status_t status = OK; if (options && options->getSeekTo(&seekTimeUs, &mode)) { - LOGV("~~~%s seekTimeUs: %lld, mode: %d", av_get_media_type_string(mMediaType), seekTimeUs, mode); + ALOGV("~~~%s seekTimeUs: %lld, mode: %d", av_get_media_type_string(mMediaType), seekTimeUs, mode); + /* add the stream start time */ + if (mStream->start_time != AV_NOPTS_VALUE) + seekTimeUs += mStream->start_time * av_q2d(mStream->time_base) * 1000000; + ALOGV("~~~%s seekTimeUs[+startTime]: %lld, mode: %d", av_get_media_type_string(mMediaType), seekTimeUs, mode); + if (mExtractor->stream_seek(seekTimeUs, mMediaType) == SEEK) seeking = true; } @@ -1415,29 +1593,53 @@ retry: } if (pkt.data == flush_pkt.data) { - LOGV("read %s flush pkt", av_get_media_type_string(mMediaType)); + ALOGV("read %s flush pkt", av_get_media_type_string(mMediaType)); av_free_packet(&pkt); + mFirstKeyPktTimestamp = AV_NOPTS_VALUE; goto retry; } else if (pkt.data == NULL && pkt.size == 0) { - LOGV("read %s eos pkt", av_get_media_type_string(mMediaType)); + ALOGD("read %s eos pkt", av_get_media_type_string(mMediaType)); av_free_packet(&pkt); mExtractor->reachedEOS(mMediaType); - return ERROR_END_OF_STREAM; + return ERROR_END_OF_STREAM; } key = pkt.flags & AV_PKT_FLAG_KEY ? 1 : 0; + pktTS = pkt.pts; //FIXME AV_NOPTS_VALUE?? + + //use dts when AVI + if (pkt.pts == AV_NOPTS_VALUE) + pktTS = pkt.dts; + + //FIXME, drop, omxcodec requires a positive timestamp! e.g. vorbis + if (pktTS != AV_NOPTS_VALUE && pktTS < 0) { + ALOGW("drop the packet with negative timestamp(pts:%lld)", pktTS); + av_free_packet(&pkt); + goto retry; + } if (waitKeyPkt) { if (!key) { - LOGV("drop the no key packet"); + ALOGV("drop the non-key packet"); av_free_packet(&pkt); goto retry; } else { - LOGV("~~~~~~ got the key packet"); + ALOGV("~~~~~~ got the key packet"); waitKeyPkt = false; } } + + if (pktTS != AV_NOPTS_VALUE && mFirstKeyPktTimestamp == AV_NOPTS_VALUE) { + // update the first key timestamp + mFirstKeyPktTimestamp = pktTS; + } + if (pktTS != AV_NOPTS_VALUE && pktTS < mFirstKeyPktTimestamp) { + ALOGV("drop the packet with the backward timestamp, maybe they are B-frames after I-frame ^_^"); + av_free_packet(&pkt); + goto retry; + } + MediaBuffer *mediaBuffer = new MediaBuffer(pkt.size + FF_INPUT_BUFFER_PADDING_SIZE); mediaBuffer->meta_data()->clear(); mediaBuffer->set_range(0, pkt.size); @@ -1464,8 +1666,8 @@ retry: status = ERROR_MALFORMED; break; } - dst += mNALLengthSize; - ptr += mNALLengthSize; + dst += mNALLengthSize; + ptr += mNALLengthSize; len -= mNALLengthSize; memcpy(dst, ptr, nal_len); @@ -1479,7 +1681,7 @@ retry: } if (status != OK) { - LOGV("status != OK"); + ALOGV("status != OK"); mediaBuffer->release(); mediaBuffer = NULL; av_free_packet(&pkt); @@ -1489,30 +1691,19 @@ retry: memcpy(mediaBuffer->data(), pkt.data, pkt.size); } - pktTS = pkt.pts; - // use dts when AVI - if (pkt.pts == AV_NOPTS_VALUE) - pktTS = pkt.dts; - -#if 0 - // TODO, Stagefright can't handle negative timestamps - // if needed, work around this by offsetting them manually? - if (pktTS < 0) - pktTS = 0; -#endif - - timeUs = (int64_t)(pktTS * av_q2d(mStream->time_base) * 1000000); - -#if 0 - LOGV("read %s pkt, size: %d, key: %d, pts: %lld, dts: %lld, timeUs: %llu us (%.2f secs)", - av_get_media_type_string(mMediaType), pkt.size, key, pkt.pts, pkt.dts, timeUs, timeUs/1E6); -#endif + int64_t start_time = mStream->start_time != AV_NOPTS_VALUE ? mStream->start_time : 0; + if (pktTS != AV_NOPTS_VALUE) + timeUs = (int64_t)((pktTS - start_time) * av_q2d(mStream->time_base) * 1000000); + else + timeUs = SF_NOPTS_VALUE; //FIXME AV_NOPTS_VALUE is negative, but stagefright need positive -#if 0 - // TODO, Stagefright can't handle negative timestamps - // if needed, work around this by offsetting them manually? - if (timeUs < 0) - timeUs = 0; +#if DEBUG_PKT + if (pktTS != AV_NOPTS_VALUE) + ALOGV("read %s pkt, size:%d, key:%d, pts:%lld, dts:%lld, timeUs[-startTime]:%lld us (%.2f secs)", + av_get_media_type_string(mMediaType), pkt.size, key, pkt.pts, pkt.dts, timeUs, timeUs/1E6); + else + ALOGV("read %s pkt, size:%d, key:%d, pts:N/A, dts:N/A, timeUs[-startTime]:N/A", + av_get_media_type_string(mMediaType), pkt.size, key); #endif mediaBuffer->meta_data()->setInt64(kKeyTime, timeUs); @@ -1527,88 +1718,336 @@ retry: //////////////////////////////////////////////////////////////////////////////// -// TODO: Temp hack typedef struct { - const char *extension; + const char *format; const char *container; -} extmap; - -static extmap FILE_EXTS [] = { - {".mp4", MEDIA_MIMETYPE_CONTAINER_MPEG4}, - {".3gp", MEDIA_MIMETYPE_CONTAINER_MPEG4}, - {".mp3", MEDIA_MIMETYPE_AUDIO_MPEG}, - {".mov", MEDIA_MIMETYPE_CONTAINER_MOV}, - {".mkv", MEDIA_MIMETYPE_CONTAINER_MATROSKA}, - {".ts", MEDIA_MIMETYPE_CONTAINER_TS}, - {".avi", MEDIA_MIMETYPE_CONTAINER_AVI}, -#if 0 - {".asf", MEDIA_MIMETYPE_CONTAINER_ASF}, - {".wmv", MEDIA_MIMETYPE_CONTAINER_WMV}, - {".wma", MEDIA_MIMETYPE_CONTAINER_WMA}, - {".mpg", MEDIA_MIMETYPE_CONTAINER_MPG}, - {".flv", MEDIA_MIMETYPE_CONTAINER_FLV}, - {".divx", MEDIA_MIMETYPE_CONTAINER_DIVX}, - {".mp2", MEDIA_MIMETYPE_CONTAINER_MP2}, - {".ape", MEDIA_MIMETYPE_CONTAINER_APE}, - {".rm ", MEDIA_MIMETYPE_CONTAINER_RM}, - {".ra", MEDIA_MIMETYPE_CONTAINER_RA}, -#endif +} formatmap; + +static formatmap FILE_FORMATS[] = { + {"mpeg", MEDIA_MIMETYPE_CONTAINER_MPEG2PS }, + {"mpegts", MEDIA_MIMETYPE_CONTAINER_TS }, + {"mov,mp4,m4a,3gp,3g2,mj2", MEDIA_MIMETYPE_CONTAINER_MPEG4 }, + {"matroska,webm", MEDIA_MIMETYPE_CONTAINER_MATROSKA }, + {"asf", MEDIA_MIMETYPE_CONTAINER_ASF }, + {"rm", MEDIA_MIMETYPE_CONTAINER_RM }, + {"flv", MEDIA_MIMETYPE_CONTAINER_FLV }, + {"swf", MEDIA_MIMETYPE_CONTAINER_FLV }, + {"avi", MEDIA_MIMETYPE_CONTAINER_AVI }, + {"ape", MEDIA_MIMETYPE_CONTAINER_APE }, + {"dts", MEDIA_MIMETYPE_CONTAINER_DTS }, + {"flac", MEDIA_MIMETYPE_CONTAINER_FLAC }, + {"ac3", MEDIA_MIMETYPE_AUDIO_AC3 }, + {"wav", MEDIA_MIMETYPE_CONTAINER_WAV }, + {"ogg", MEDIA_MIMETYPE_CONTAINER_OGG }, + {"hevc", MEDIA_MIMETYPE_CONTAINER_HEVC }, }; -// TODO: check mFormatCtx->iformat->name? -bool SniffFFMPEG( - const sp &source, String8 *mimeType, float *confidence, - sp *meta) { - LOGV("SniffFFMPEG"); - size_t i; - const char *uri, *container = NULL; +static void adjustMPEG4Confidence(AVFormatContext *ic, float *confidence) +{ + AVDictionary *tags = NULL; + AVDictionaryEntry *tag = NULL; + + tags = ic->metadata; + + //NOTE: You can use command to show these tags, + //e.g. "ffprobe -show_format 2012.mov" + + tag = av_dict_get(tags, "major_brand", NULL, 0); + if (!tag) { + return; + } + + ALOGV("major_brand tag is:%s", tag->value); + + //when MEDIA_MIMETYPE_CONTAINER_MPEG4 + //WTF, MPEG4Extractor.cpp can not extractor mov format + //NOTE: isCompatibleBrand(MPEG4Extractor.cpp) + // Won't promise that the following file types can be played. + // Just give these file types a chance. + // FOURCC('q', 't', ' ', ' '), // Apple's QuickTime + //So...... + if (!strcmp(tag->value, "qt ")) { + ALOGI("format is mov, confidence should be larger than mpeg4"); + //the MEDIA_MIMETYPE_CONTAINER_MPEG4 of confidence is 0.4f + *confidence = 0.41f; + } +} - //av_find_input_format()?? - uri = source->getNamURI(); +static void adjustVideoCodecConfidence(AVFormatContext *ic, + enum AVCodecID codec_id, float *confidence) +{ + //add to here +} - if (!uri) - return false; +//TODO. if the other stream(e.g. mp3) is supported by stagefright +static void adjustAudioCodecConfidence(AVFormatContext *ic, + enum AVCodecID codec_id, float *confidence) +{ + switch (codec_id) { + case AV_CODEC_ID_AC3: + ALOGI("ffmpeg can demux ac3 only"); + *confidence = 0.88f; + break; + case AV_CODEC_ID_MP1: + case AV_CODEC_ID_MP2: + //TODO. if the other stream(e.g. mp3) is supported by stagefright + ALOGI("ffmpeg can demux mp1 and mp2 only"); + *confidence = 0.88f; + break; + default: + break; + } +} - LOGI("ffmpeg uri: %s", uri); +static void adjustCodecConfidence(AVFormatContext *ic, float *confidence) +{ + unsigned int idx = 0; + AVCodecContext *avctx = NULL; + AVMediaType codec_type = AVMEDIA_TYPE_UNKNOWN; + enum AVCodecID codec_id = AV_CODEC_ID_NONE; + bool haveVideo = false; + bool haveAudio = false; + bool haveMP3 = false; + + for (idx = 0; idx < ic->nb_streams; idx++) { + avctx = ic->streams[idx]->codec; + codec_type = avctx->codec_type; + codec_id = avctx->codec_id; + + if (codec_type == AVMEDIA_TYPE_VIDEO) { + haveVideo = true; + adjustVideoCodecConfidence(ic, codec_id, confidence); + } else if (codec_type == AVMEDIA_TYPE_AUDIO) { + haveAudio = true; + adjustAudioCodecConfidence(ic, codec_id, confidence); + if (codec_id == AV_CODEC_ID_MP3) + haveMP3 = true; + } + } - LOGI("list the file extensions suppoted by ffmpeg: "); - LOGI("========================================"); - for (i = 0; i < NELEM(FILE_EXTS); ++i) { - LOGV("file_exts[%02d]: %s", i, FILE_EXTS[i].extension); - } - LOGI("========================================"); - - int lenURI = strlen(uri); - for (i = 0; i < NELEM(FILE_EXTS); ++i) { - int len = strlen(FILE_EXTS[i].extension); - int start = lenURI - len; - if (start > 0) { - if (!av_strncasecmp(uri + start, FILE_EXTS[i].extension, len)) { - container = FILE_EXTS[i].container; - break; - } - } - } + if (haveVideo && haveMP3) { + *confidence = 0.22f; // larger than MP3Extractor an MP3Extractor + } +} - if (container == NULL) - return false; +static void adjustConfidenceIfNeeded(const char *mime, + AVFormatContext *ic, float *confidence) +{ + //check mime + if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG4)) { + adjustMPEG4Confidence(ic, confidence); + } else { + //add to here; + } + + //check codec + adjustCodecConfidence(ic, confidence); +} - LOGV("found container: %s", container); +static const char *findMatchingContainer(const char *name) +{ + size_t i = 0; + const char *container = NULL; + + ALOGI("list the formats suppoted by ffmpeg: "); + ALOGI("========================================"); + for (i = 0; i < NELEM(FILE_FORMATS); ++i) { + ALOGV("format_names[%02d]: %s", i, FILE_FORMATS[i].format); + } + ALOGI("========================================"); + + for (i = 0; i < NELEM(FILE_FORMATS); ++i) { + int len = strlen(FILE_FORMATS[i].format); + if (!av_strncasecmp(name, FILE_FORMATS[i].format, len)) { + container = FILE_FORMATS[i].container; + break; + } + } - *confidence = 0.88f; // Slightly larger than other extractor's confidence - mimeType->setTo(container); + return container; +} - /* use MPEG4Extractor(not extended extractor) for HTTP source only */ - if (!av_strcasecmp(container, MEDIA_MIMETYPE_CONTAINER_MPEG4) - && (source->flags() & DataSource::kIsCachingDataSource)) { - return true; - } +static const char *SniffFFMPEGCommon(const char *url, float *confidence) +{ + size_t i = 0; + int err = 0; + const char *container = NULL; + AVFormatContext *ic = NULL; + AVDictionary **opts = NULL; + size_t orig_nb_streams = 0; + + status_t status = initFFmpeg(); + if (status != OK) { + ALOGE("could not init ffmpeg"); + return NULL; + } + + ic = avformat_alloc_context(); + if (!ic) + { + ALOGE("oom for alloc avformat context"); + goto fail; + } + + err = avformat_open_input(&ic, url, NULL, NULL); + if (err < 0) { + ALOGE("%s: avformat_open_input failed, err:%s", url, av_err2str(err)); + goto fail; + } + + opts = setup_find_stream_info_opts(ic, codec_opts); + orig_nb_streams = ic->nb_streams; + err = avformat_find_stream_info(ic, opts); + if (err < 0) { + ALOGE("%s: could not find stream info, err:%s", url, av_err2str(err)); + goto fail; + } + for (i = 0; i < orig_nb_streams; i++) { + av_dict_free(&opts[i]); + } + av_freep(&opts); + + av_dump_format(ic, 0, url, 0); + + ALOGI("FFmpegExtrator, url: %s, format_name: %s, format_long_name: %s", + url, ic->iformat->name, ic->iformat->long_name); + + container = findMatchingContainer(ic->iformat->name); + + if (container) { + adjustConfidenceIfNeeded(container, ic, confidence); + } - *meta = new AMessage; - (*meta)->setString("extended-extractor", "extended-extractor"); - (*meta)->setString("extended-extractor-subtype", "ffmpegextractor"); +fail: + if (ic) { + avformat_close_input(&ic); + av_free(ic); + } + if (status == OK) { + deInitFFmpeg(); + } + + return container; +} - return true; +static const char *LegacySniffFFMPEG(const sp &source, float *confidence) +{ + String8 uri = source->getUri(); + if (uri.empty()) { + return NULL; + } + + ALOGI("source url:%s", uri.string()); + + return SniffFFMPEGCommon(uri.string(), confidence); +} + +static const char *BetterSniffFFMPEG(const sp &source, float *confidence) +{ + char url[128] = {0}; + + ALOGI("android-source:%p", source.get()); + + // pass the addr of smart pointer("source") + snprintf(url, sizeof(url), "android-source:%p", source.get()); + + return SniffFFMPEGCommon(url, confidence); +} + +bool SniffFFMPEG( + const sp &source, String8 *mimeType, float *confidence, + sp *meta) { + ALOGV("SniffFFMPEG"); + + *confidence = 0.08f; // be the last resort, by default + + const char *container = BetterSniffFFMPEG(source, confidence); + if (!container) { + ALOGW("sniff through BetterSniffFFMPEG failed, try LegacySniffFFMPEG"); + container = LegacySniffFFMPEG(source, confidence); + if (container) { + ALOGI("sniff through LegacySniffFFMPEG success"); + } + } else { + ALOGI("sniff through BetterSniffFFMPEG success"); + } + + if (container == NULL) { + ALOGD("SniffFFMPEG failed to sniff this source"); + return false; + } + + ALOGD("ffmpeg detected media content as '%s' with confidence %.2f", + container, *confidence); + + /* use MPEG4Extractor(not extended extractor) for HTTP source only */ + if (!strcasecmp(container, MEDIA_MIMETYPE_CONTAINER_MPEG4) + && (source->flags() & DataSource::kIsCachingDataSource)) { + ALOGI("support container: %s, but it is caching data source, " + "Don't use ffmpegextractor", container); + return false; + } + + mimeType->setTo(container); + + *meta = new AMessage; + (*meta)->setString("extended-extractor", "extended-extractor"); + (*meta)->setString("extended-extractor-subtype", "ffmpegextractor"); + + //debug only + char value[PROPERTY_VALUE_MAX]; + property_get("sys.media.parser.ffmpeg", value, "0"); + if (atoi(value)) { + ALOGI("[debug] parser use ffmpeg"); + *confidence = 0.88f; + } + + if (*confidence > 0.08f) { + (*meta)->setString("extended-extractor-use", "ffmpegextractor"); + } + + return true; +} + +MediaExtractor *CreateFFmpegExtractor(const sp &source, const char *mime, const sp &meta) { + MediaExtractor *ret = NULL; + AString notuse; + if (meta.get() && meta->findString("extended-extractor", ¬use) && ( + !strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG4) || + !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_MPEG) || + !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AC3) || + !strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MOV) || + !strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MATROSKA) || + !strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_TS) || + !strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2PS) || + !strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_AVI) || + !strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_ASF) || + !strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_WEBM) || + !strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_WMV) || + !strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPG) || + !strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_FLV) || + !strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_DIVX) || + !strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_RM) || + !strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_WAV) || + !strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_FLAC) || + !strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_APE) || + !strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_DTS) || + !strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MP2) || + !strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_RA) || + !strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_OGG) || + !strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_HEVC) || + !strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_WMA))) { + ret = new FFmpegExtractor(source); + } + + ALOGD("%ssupported mime: %s", (ret ? "" : "un"), mime); + return ret; } } // namespace android + +extern "C" void getExtractorPlugin(android::MediaExtractor::Plugin *plugin) +{ + plugin->sniff = android::SniffFFMPEG; + plugin->create = android::CreateFFmpegExtractor; +}