#include "FFmpegExtractor.h"
-#define MAX_QUEUE_SIZE (15 * 1024 * 1024)
-#define MIN_AUDIOQ_SIZE (20 * 16 * 1024)
+#define MAX_QUEUE_SIZE (40 * 1024 * 1024)
+#define MIN_AUDIOQ_SIZE (2 * 1024 * 1024)
#define MIN_FRAMES 5
#define EXTRACTOR_MAX_PROBE_PACKETS 200
#define FF_MAX_EXTRADATA_SIZE ((1 << 28) - FF_INPUT_BUFFER_PADDING_SIZE)
mInitCheck(NO_INIT),
mFFmpegInited(false),
mFormatCtx(NULL),
- mReaderThreadStarted(false) {
+ mReaderThreadStarted(false),
+ mParsedMetadata(false) {
ALOGV("FFmpegExtractor::FFmpegExtractor");
fetchStuffsFromSniffedMeta(meta);
}
/* Quick and dirty, just get a frame 1/4 in */
- if (mTracks.itemAt(index).mIndex == mVideoStreamIdx) {
- int64_t thumb_ts = av_rescale_q((mFormatCtx->streams[mVideoStreamIdx]->duration / 4),
- mFormatCtx->streams[mVideoStreamIdx]->time_base, AV_TIME_BASE_Q);
- mTracks.itemAt(index).mMeta->setInt64(kKeyThumbnailTime, thumb_ts);
+ if (mTracks.itemAt(index).mIndex == mVideoStreamIdx &&
+ mFormatCtx->duration != AV_NOPTS_VALUE) {
+ mTracks.itemAt(index).mMeta->setInt64(
+ kKeyThumbnailTime, mFormatCtx->duration / 4);
}
return mTracks.itemAt(index).mMeta;
return NULL;
}
+ if (!mParsedMetadata) {
+ parseMetadataTags(mFormatCtx, mMeta);
+ mParsedMetadata = true;
+ }
+
return mMeta;
}
}
if (meta != NULL) {
+ // rotation
+ double theta = get_rotation(stream);
+ int rotationDegrees = 0;
+
+ if (fabs(theta - 90) < 1.0) {
+ rotationDegrees = 90;
+ } else if (fabs(theta - 180) < 1.0) {
+ rotationDegrees = 180;
+ } else if (fabs(theta - 270) < 1.0) {
+ rotationDegrees = 270;
+ }
+ if (rotationDegrees != 0) {
+ meta->setInt32(kKeyRotation, rotationDegrees);
+ }
+ }
+
+ if (meta != NULL) {
float aspect_ratio;
int width, height;
if (avctx->bit_rate > 0) {
meta->setInt32(kKeyBitRate, avctx->bit_rate);
}
+ meta->setCString('ffmt', findMatchingContainer(mFormatCtx->iformat->name));
setDurationMetaData(stream, meta);
}
meta->setInt32(kKeyBlockAlign, avctx->block_align);
meta->setInt32(kKeySampleFormat, avctx->sample_fmt);
meta->setInt32('pfmt', to_android_audio_format(avctx->sample_fmt));
+ meta->setCString('ffmt', findMatchingContainer(mFormatCtx->iformat->name));
setDurationMetaData(stream, meta);
}
if (!supported) {
ALOGE("unsupport the codec(%s)", avcodec_get_name(avctx->codec_id));
return -1;
- } else if (mFormatCtx->streams[stream_index]->disposition & AV_DISPOSITION_ATTACHED_PIC) {
+ } else if ((mFormatCtx->streams[stream_index]->disposition & AV_DISPOSITION_ATTACHED_PIC) ||
+ avctx->codec_tag == MKTAG('j', 'p', 'e', 'g')) {
ALOGD("not opening attached picture(%s)", avcodec_get_name(avctx->codec_id));
return -1;
}
- ALOGI("support the codec(%s)", avcodec_get_name(avctx->codec_id));
+ ALOGI("support the codec(%s) disposition(%x)", avcodec_get_name(avctx->codec_id), mFormatCtx->streams[stream_index]->disposition);
unsigned streamType;
for (size_t i = 0; i < mTracks.size(); ++i) {
switch (mode) {
case MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC:
- mSeekMin = INT64_MIN;
+ mSeekMin = 0;
mSeekMax = mSeekPos;
break;
case MediaSource::ReadOptions::SEEK_NEXT_SYNC:
mSeekMax = INT64_MAX;
break;
case MediaSource::ReadOptions::SEEK_CLOSEST_SYNC:
- mSeekMin = INT64_MIN;
+ mSeekMin = 0;
mSeekMax = INT64_MAX;
break;
case MediaSource::ReadOptions::SEEK_CLOSEST:
- mSeekMin = INT64_MIN;
+ mSeekMin = 0;
mSeekMax = mSeekPos;
break;
default:
ALOGV("readerEntry, full(wtf!!!), 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
+ // avoid deadlock, the audio and video data in the video is offset too large.
+ if ((mAudioQ.size == 0) && mAudioQ.wait_for_data) {
+ ALOGE("abort audio queue, since offset to video data too large");
+ packet_queue_abort(&mAudioQ);
+ }
+ if ((mVideoQ.size == 0) && mVideoQ.wait_for_data) {
+ ALOGE("abort video queue, since offset to audio data too large");
+ packet_queue_abort(&mVideoQ);
+ }
/* wait 10 ms */
mExtractorMutex.lock();
mCondition.waitRelative(mExtractorMutex, milliseconds(10));
if (ret < 0) {
mEOF = true;
eof = true;
- if (mFormatCtx->pb && mFormatCtx->pb->error) {
+ if (mFormatCtx->pb && mFormatCtx->pb->error &&
+ mFormatCtx->pb->error != ERROR_END_OF_STREAM) {
ALOGE("mFormatCtx->pb->error: %d", mFormatCtx->pb->error);
break;
}
memcpy(avctx->extradata, pkt->data, avctx->extradata_size);
memset(avctx->extradata + i, 0, FF_INPUT_BUFFER_PADDING_SIZE);
} else {
- av_free_packet(pkt);
+ av_packet_unref(pkt);
continue;
}
pkt->data, pkt->size, pkt->flags & AV_PKT_FLAG_KEY);
if (ret < 0 ||!outbuf_size) {
- av_free_packet(pkt);
+ av_packet_unref(pkt);
continue;
}
if (outbuf && outbuf != pkt->data) {
}
if (mDefersToCreateAudioTrack) {
if (avctx->extradata_size <= 0) {
- av_free_packet(pkt);
+ av_packet_unref(pkt);
continue;
}
stream_component_open(mAudioStreamIdx);
} else if (pkt->stream_index == mVideoStreamIdx) {
packet_queue_put(&mVideoQ, pkt);
} else {
- av_free_packet(pkt);
+ av_packet_unref(pkt);
}
}
mNal2AnnexB = true;
} else if (avctx->codec_id == AV_CODEC_ID_HEVC
- && avctx->extradata_size > 0) {
+ && avctx->extradata_size > 3
+ && (avctx->extradata[0] || avctx->extradata[1] ||
+ avctx->extradata[2] > 1)) {
+ /* It seems the extradata is encoded as hvcC format.
+ * Temporarily, we support configurationVersion==0 until 14496-15 3rd
+ * is finalized. When finalized, configurationVersion will be 1 and we
+ * can recognize hvcC by checking if avctx->extradata[0]==1 or not. */
mIsHEVC = true;
uint32_t type;
mNal2AnnexB = true;
}
+
}
mMediaType = mStream->codec->codec_type;
mExtractor = NULL;
}
-status_t FFmpegSource::start(MetaData *params __unused) {
+status_t FFmpegSource::start(MetaData * /* params */) {
ALOGV("FFmpegSource::start %s",
av_get_media_type_string(mMediaType));
return OK;
int64_t timeUs = AV_NOPTS_VALUE;
int key = 0;
status_t status = OK;
+ int max_negative_time_frame = 100;
int64_t startTimeUs = mStream->start_time == AV_NOPTS_VALUE ? 0 :
av_rescale_q(mStream->start_time, mStream->time_base, AV_TIME_BASE_Q);
if (seeking) {
if (pkt.data != mQueue->flush_pkt.data) {
- av_free_packet(&pkt);
+ av_packet_unref(&pkt);
goto retry;
} else {
seeking = false;
if (pkt.data == mQueue->flush_pkt.data) {
ALOGV("read %s flush pkt", av_get_media_type_string(mMediaType));
- av_free_packet(&pkt);
+ av_packet_unref(&pkt);
mFirstKeyPktTimestamp = AV_NOPTS_VALUE;
goto retry;
} else if (pkt.data == NULL && pkt.size == 0) {
ALOGD("read %s eos pkt", av_get_media_type_string(mMediaType));
- av_free_packet(&pkt);
+ av_packet_unref(&pkt);
mExtractor->reachedEOS(mMediaType);
return ERROR_END_OF_STREAM;
}
if (waitKeyPkt) {
if (!key) {
ALOGV("drop the non-key packet");
- av_free_packet(&pkt);
+ av_packet_unref(&pkt);
goto retry;
} else {
ALOGV("~~~~~~ got the key packet");
//copy data
if ((mIsAVC || mIsHEVC) && mNal2AnnexB) {
/* This only works for NAL sizes 3-4 */
- CHECK(mNALLengthSize == 3 || mNALLengthSize == 4);
+ if ((mNALLengthSize != 3) && (mNALLengthSize != 4)) {
+ ALOGE("cannot use convertNal2AnnexB, nal size: %d", mNALLengthSize);
+ mediaBuffer->release();
+ mediaBuffer = NULL;
+ av_packet_unref(&pkt);
+ return ERROR_MALFORMED;
+ }
uint8_t *dst = (uint8_t *)mediaBuffer->data();
/* Convert H.264 NAL format to annex b */
ALOGE("convertNal2AnnexB failed");
mediaBuffer->release();
mediaBuffer = NULL;
- av_free_packet(&pkt);
+ av_packet_unref(&pkt);
return ERROR_MALFORMED;
}
} else {
else
timeUs = SF_NOPTS_VALUE; //FIXME AV_NOPTS_VALUE is negative, but stagefright need positive
+ // Negative timestamp will cause crash for media_server
+ // in OMXCodec.cpp CHECK(lastBufferTimeUs >= 0).
+ // And we should not get negative timestamp
+ if (timeUs < 0) {
+ ALOGE("negative timestamp encounter: time: %" PRId64
+ " startTimeUs: %" PRId64
+ " packet dts: %" PRId64
+ " packet pts: %" PRId64
+ , timeUs, startTimeUs, pkt.dts, pkt.pts);
+ mediaBuffer->release();
+ mediaBuffer = NULL;
+ av_packet_unref(&pkt);
+ if (max_negative_time_frame-- > 0) {
+ goto retry;
+ } else {
+ ALOGE("too many negative timestamp packets, abort decoding");
+ return ERROR_MALFORMED;
+ }
+ }
+
// predict the next PTS to use for exact-frame seek below
int64_t nextPTS = AV_NOPTS_VALUE;
if (mLastPTS != AV_NOPTS_VALUE && timeUs > mLastPTS) {
*buffer = mediaBuffer;
- av_free_packet(&pkt);
+ av_packet_unref(&pkt);
return OK;
}
}
avctx = ic->streams[idx]->codec;
+ if (avctx->codec_tag == MKTAG('j', 'p', 'e', 'g')) {
+ // Sometimes the disposition isn't set
+ continue;
+ }
if (avctx->codec_type == codec_type) {
return avctx;
}
return supported;
}
-static void adjustMPEG4Confidence(AVFormatContext *ic, float *confidence)
+static void adjustMPEG4Confidence(AVFormatContext *ic, float *confidence, bool isStreaming)
{
AVDictionary *tags = NULL;
AVDictionaryEntry *tag = NULL;
enum AVCodecID codec_id = AV_CODEC_ID_NONE;
+ bool is_mov = false;
//1. check codec id
codec_id = getCodecId(ic, AVMEDIA_TYPE_VIDEO);
//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;
+ if (tag) {
+ 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("[mp4]format is mov, confidence should be larger than mpeg4");
+ *confidence = 0.41f;
+ is_mov = true;
+ }
+ }
+ if (isStreaming && !is_mov) {
+ ALOGI("support container: video/mp4, but it is caching data source, "
+ "Don't use ffmpegextractor");
+ *confidence = 0; // MP4 and streaming, use AOSP
}
+}
- ALOGV("major_brand tag is:%s", tag->value);
+static void adjustMPEG2PSConfidence(AVFormatContext *ic, float *confidence)
+{
+ enum AVCodecID codec_id = AV_CODEC_ID_NONE;
- //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("[mp4]format is mov, confidence should be larger than mpeg4");
- *confidence = 0.41f;
+ codec_id = getCodecId(ic, AVMEDIA_TYPE_VIDEO);
+ if (codec_id != AV_CODEC_ID_NONE
+ && codec_id != AV_CODEC_ID_H264
+ && codec_id != AV_CODEC_ID_MPEG4
+ && codec_id != AV_CODEC_ID_MPEG1VIDEO
+ && codec_id != AV_CODEC_ID_MPEG2VIDEO) {
+ //the MEDIA_MIMETYPE_CONTAINER_MPEG2TS of confidence is 0.25f
+ ALOGI("[mpeg2ps]video codec(%s), confidence should be larger than MPEG2PSExtractor",
+ avcodec_get_name(codec_id));
+ *confidence = 0.26f;
+ }
+
+ codec_id = getCodecId(ic, AVMEDIA_TYPE_AUDIO);
+ if (codec_id != AV_CODEC_ID_NONE
+ && codec_id != AV_CODEC_ID_AAC
+ && codec_id != AV_CODEC_ID_PCM_S16LE
+ && codec_id != AV_CODEC_ID_PCM_S24LE
+ && codec_id != AV_CODEC_ID_MP1
+ && codec_id != AV_CODEC_ID_MP2
+ && codec_id != AV_CODEC_ID_MP3) {
+ ALOGI("[mpeg2ps]audio codec(%s), confidence should be larger than MPEG2PSExtractor",
+ avcodec_get_name(codec_id));
+ *confidence = 0.26f;
}
}
//TODO need more checks
static void adjustConfidenceIfNeeded(const char *mime,
- AVFormatContext *ic, float *confidence)
+ AVFormatContext *ic, float *confidence, bool isStreaming)
{
//1. check mime
if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG4)) {
- adjustMPEG4Confidence(ic, confidence);
+ adjustMPEG4Confidence(ic, confidence, isStreaming);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2TS)) {
adjustMPEG2TSConfidence(ic, confidence);
+ } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2PS)) {
+ adjustMPEG2PSConfidence(ic, confidence);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MATROSKA)) {
adjustMKVConfidence(ic, confidence);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_DIVX)) {
//todo here
}
- if (*confidence > 0.08) {
- return;
- }
-
//2. check codec
adjustCodecConfidence(ic, confidence);
}
const char *container = NULL;
#endif
- ALOGV("list the formats suppoted by ffmpeg: ");
- ALOGV("========================================");
- for (i = 0; i < NELEM(FILE_FORMATS); ++i) {
- ALOGV("format_names[%02d]: %s", i, FILE_FORMATS[i].format);
- }
- ALOGV("========================================");
-
for (i = 0; i < NELEM(FILE_FORMATS); ++i) {
int len = strlen(FILE_FORMATS[i].format);
if (!strncasecmp(name, FILE_FORMATS[i].format, len)) {
return container;
}
-static const char *SniffFFMPEGCommon(const char *url, float *confidence, bool fastMPEG4)
+static const char *SniffFFMPEGCommon(const char *url, float *confidence, bool isStreaming)
{
int err = 0;
size_t i = 0;
size_t nb_streams = 0;
+ int64_t timeNow = 0;
const char *container = NULL;
AVFormatContext *ic = NULL;
AVDictionary *codec_opts = NULL;
AVDictionary **opts = NULL;
+ bool needProbe = false;
status_t status = initFFmpeg();
if (status != OK) {
goto fail;
}
+ // Don't download more than a meg
+ ic->probesize = 1024 * 1024;
+
+ timeNow = ALooper::GetNowUs();
+
err = avformat_open_input(&ic, url, NULL, NULL);
if (err < 0) {
goto fail;
}
- if (ic->iformat != NULL && ic->iformat->name != NULL &&
- findMatchingContainer(ic->iformat->name) != NULL &&
- !strcasecmp(findMatchingContainer(ic->iformat->name),
- MEDIA_MIMETYPE_CONTAINER_MPEG4)) {
- if (fastMPEG4) {
- container = findMatchingContainer(ic->iformat->name);
- goto fail;
- }
+ if (ic->iformat != NULL && ic->iformat->name != NULL) {
+ container = findMatchingContainer(ic->iformat->name);
}
- opts = setup_find_stream_info_opts(ic, codec_opts);
- 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 < nb_streams; i++) {
- av_dict_free(&opts[i]);
+ ALOGV("opened, nb_streams: %d container: %s delay: %.2f ms", ic->nb_streams, container,
+ ((float)ALooper::GetNowUs() - timeNow) / 1000);
+
+ // Only probe if absolutely necessary. For formats with headers, avformat_open_input will
+ // figure out the components.
+ for (unsigned int i = 0; i < ic->nb_streams; i++) {
+ AVStream* stream = ic->streams[i];
+ if (!stream->codec || !stream->codec->codec_id) {
+ needProbe = true;
+ break;
+ }
+ ALOGV("found stream %d id %d codec %s", i, stream->codec->codec_id, avcodec_get_name(stream->codec->codec_id));
}
- av_freep(&opts);
- av_dump_format(ic, 0, url, 0);
+ // We must go deeper.
+ if (!isStreaming && (!ic->nb_streams || needProbe)) {
+ timeNow = ALooper::GetNowUs();
+
+ opts = setup_find_stream_info_opts(ic, codec_opts);
+ 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;
+ }
+
+ ALOGV("probed stream info after %.2f ms", ((float)ALooper::GetNowUs() - timeNow) / 1000);
- ALOGD("FFmpegExtrator, url: %s, format_name: %s, format_long_name: %s",
+ for (i = 0; i < nb_streams; i++) {
+ av_dict_free(&opts[i]);
+ }
+ av_freep(&opts);
+
+ av_dump_format(ic, 0, url, 0);
+ }
+
+ ALOGV("url: %s, format_name: %s, format_long_name: %s",
url, ic->iformat->name, ic->iformat->long_name);
container = findMatchingContainer(ic->iformat->name);
if (container) {
adjustContainerIfNeeded(&container, ic);
- adjustConfidenceIfNeeded(container, ic, confidence);
+ adjustConfidenceIfNeeded(container, ic, confidence, isStreaming);
+ if (*confidence == 0)
+ container = NULL;
}
fail:
// pass the addr of smart pointer("source")
snprintf(url, sizeof(url), "android-source:%p", source.get());
- ret = SniffFFMPEGCommon(url, confidence, (source->flags() & DataSource::kIsCachingDataSource));
+ ret = SniffFFMPEGCommon(url, confidence,
+ (source->flags() & DataSource::kIsCachingDataSource));
if (ret) {
meta->setString("extended-extractor-url", url);
}
return NULL;
}
+ if (source->flags() & DataSource::kIsCachingDataSource)
+ return NULL;
+
ALOGV("source url:%s", uri.string());
// pass the addr of smart pointer("source") + file name
bool SniffFFMPEG(
const sp<DataSource> &source, String8 *mimeType, float *confidence,
sp<AMessage> *meta) {
- ALOGV("SniffFFMPEG");
+
+ float newConfidence = 0.08f;
+
+ ALOGV("SniffFFMPEG (initial confidence: %f, mime: %s)", *confidence,
+ mimeType == NULL ? "unknown" : *mimeType);
+
+ // This is a heavyweight sniffer, don't invoke it if Stagefright knows
+ // what it is doing already.
+ if (mimeType != NULL && confidence != NULL) {
+ if (*mimeType == "application/ogg") {
+ return false;
+ }
+ if (*confidence > 0.8f) {
+ return false;
+ }
+ }
*meta = new AMessage;
- *confidence = 0.08f; // be the last resort, by default
- const char *container = BetterSniffFFMPEG(source, confidence, *meta);
+ const char *container = BetterSniffFFMPEG(source, &newConfidence, *meta);
if (!container) {
ALOGW("sniff through BetterSniffFFMPEG failed, try LegacySniffFFMPEG");
- container = LegacySniffFFMPEG(source, confidence, *meta);
+ container = LegacySniffFFMPEG(source, &newConfidence, *meta);
if (container) {
ALOGV("sniff through LegacySniffFFMPEG success");
}
}
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);
- (*meta)->clear();
- *meta = NULL;
- return false;
- }
+ container, newConfidence);
mimeType->setTo(container);
property_get("sys.media.parser.ffmpeg", value, "0");
if (atoi(value)) {
ALOGD("[debug] use ffmpeg parser");
- *confidence = 0.88f;
+ newConfidence = 0.88f;
}
- if (*confidence > 0.08f) {
+ if (newConfidence > *confidence) {
(*meta)->setString("extended-extractor-use", "ffmpegextractor");
+ *confidence = newConfidence;
}
return true;