播放器有个功能,当用户打开视频时,需要读取媒体文件的总时长等信息,不巧的时,获取FLV时总失败,下面来具体分析下FLV和MP4获取总时长的原因和区别:
播放器有个获取MediaInfo的接口,功能如下:
int MediaFFmpeg::DecoderGetMediaInfo(MediaInfo *mi,AVCodecContext *decodec_ctx,AVStream *stream){
if(!mi || !stream || !decodec_ctx){
return -1;
}
//video
if(stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){
//获取视频总时长
if(AV_NOPTS_VALUE != stream->duration)
{
mi->duration = stream->duration * av_q2d(stream->time_base);
std::cout << "video_time : " <<
(mi->duration / 3600)<< ":" <<
(mi->duration % 3600) / 60<< ":" <<
(mi->duration % 60) << std::endl;
char formatStr[128] = {0,};
sprintf(formatStr, "%02d:%02d:%02d",
(mi->duration / 3600),
((mi->duration % 3600) / 60),
(mi->duration % 60));
mi->durationFormatStr = formatStr;
}
else{
printf("audio duration unknown ! \n");
}
mi->width = stream->codecpar->width;
mi->height = stream->codecpar->height;
}
//audio
else if(stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO){
mi->sample_fmt = AV_SAMPLE_FMT_S16;
mi->sample_rate = decodec_ctx->sample_rate; //采样率/*48000; */
mi->channels = decodec_ctx->channels; //通道数/*1; */
mi->nb_samples = decodec_ctx->frame_size;/*1024; */
mi->audio_buffer_size = av_samples_get_buffer_size(NULL, mi->channels, mi->nb_samples, (enum AVSampleFormat)mi->sample_fmt, 1); //输出buff
}
return 0;
}
有经验的人可能很快就能看出来是否存在问题。
总是打印duration不合法:
很奇怪的是,使用av_dump_format函数可以看到Duration:
Input #0, flv, from '/home/zhenghui/视频/1080P.flv':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2avc1mp41
description : Packed by Bilibili XCoder v2.0.2
encoder : Lavf60.3.100
Duration: 00:03:46.53, start: 0.000000, bitrate: 3309 kb/s
Stream #0:0: Video: flv1, yuv420p, 1920x1080, 200 kb/s, 30 fps, 30 tbr, 1k tbn
Stream #0:1: Audio: adpcm_swf, 44100 Hz, stereo, s16, 352 kb/s
就翻了翻ffmpeg的源码,找到了av_dump_format的源码:
void av_dump_format(AVFormatContext *ic, int index,
const char *url, int is_output)
{
int i;
uint8_t *printed = ic->nb_streams ? av_mallocz(ic->nb_streams) : NULL;
if (ic->nb_streams && !printed)
return;
av_log(NULL, AV_LOG_INFO, "%s #%d, %s, %s '%s':\n",
is_output ? "Output" : "Input",
index,
is_output ? ic->oformat->name : ic->iformat->name,
is_output ? "to" : "from", url);
dump_metadata(NULL, ic->metadata, " ");
if (!is_output) {
av_log(NULL, AV_LOG_INFO, " Duration: ");
if (ic->duration != AV_NOPTS_VALUE) {
int64_t hours, mins, secs, us;
int64_t duration = ic->duration + (ic->duration <= INT64_MAX - 5000 ? 5000 : 0);
secs = duration / AV_TIME_BASE;
us = duration % AV_TIME_BASE;
mins = secs / 60;
secs %= 60;
hours = mins / 60;
mins %= 60;
av_log(NULL, AV_LOG_INFO, "%02"PRId64":%02"PRId64":%02"PRId64".%02"PRId64"", hours, mins, secs,
(100 * us) / AV_TIME_BASE);
} else {
av_log(NULL, AV_LOG_INFO, "N/A");
}
if (ic->start_time != AV_NOPTS_VALUE) {
int secs, us;
av_log(NULL, AV_LOG_INFO, ", start: ");
secs = llabs(ic->start_time / AV_TIME_BASE);
us = llabs(ic->start_time % AV_TIME_BASE);
av_log(NULL, AV_LOG_INFO, "%s%d.%06d",
ic->start_time >= 0 ? "" : "-",
secs,
(int) av_rescale(us, 1000000, AV_TIME_BASE));
}
av_log(NULL, AV_LOG_INFO, ", bitrate: ");
if (ic->bit_rate)
av_log(NULL, AV_LOG_INFO, "%"PRId64" kb/s", ic->bit_rate / 1000);
else
av_log(NULL, AV_LOG_INFO, "N/A");
av_log(NULL, AV_LOG_INFO, "\n");
}
if (ic->nb_chapters)
av_log(NULL, AV_LOG_INFO, " Chapters:\n");
for (i = 0; i < ic->nb_chapters; i++) {
const AVChapter *ch = ic->chapters[i];
av_log(NULL, AV_LOG_INFO, " Chapter #%d:%d: ", index, i);
av_log(NULL, AV_LOG_INFO,
"start %f, ", ch->start * av_q2d(ch->time_base));
av_log(NULL, AV_LOG_INFO,
"end %f\n", ch->end * av_q2d(ch->time_base));
dump_metadata(NULL, ch->metadata, " ");
}
if (ic->nb_programs) {
int j, k, total = 0;
for (j = 0; j < ic->nb_programs; j++) {
const AVProgram *program = ic->programs[j];
const AVDictionaryEntry *name = av_dict_get(program->metadata,
"name", NULL, 0);
av_log(NULL, AV_LOG_INFO, " Program %d %s\n", program->id,
name ? name->value : "");
dump_metadata(NULL, program->metadata, " ");
for (k = 0; k < program->nb_stream_indexes; k++) {
dump_stream_format(ic, program->stream_index[k],
index, is_output);
printed[program->stream_index[k]] = 1;
}
total += program->nb_stream_indexes;
}
if (total < ic->nb_streams)
av_log(NULL, AV_LOG_INFO, " No Program\n");
}
for (i = 0; i < ic->nb_streams; i++)
if (!printed[i])
dump_stream_format(ic, i, index, is_output);
av_free(printed);
}
av_dump_format函数中使用的是AVFormatContext中的duration,而我使用的是AVStream的duration。
Debug了一下:AVFormatContext中的duration确实存在:
继续跟踪到AVStream的调用位置,确实不存在:
最终修改如下得已解决:
int MediaFFmpeg::DecoderGetMediaInfo(MediaInfo *mi,AVFormatContext *ic,AVCodecContext *decodec_ctx,AVStream *stream){
if(!mi || !stream || !decodec_ctx){
return -1;
}
//video
if(stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){
//获取视频总时长
if(AV_NOPTS_VALUE != ic->duration)
{
int64_t hours, mins, secs, us;
int64_t duration = ic->duration + (ic->duration <= INT64_MAX - 5000 ? 5000 : 0);
secs = duration / AV_TIME_BASE;
us = duration % AV_TIME_BASE;
mins = secs / 60;
secs %= 60;
hours = mins / 60;
mins %= 60;
mi->duration = duration;
char formatStr[128] = {0,};
sprintf(formatStr, "%02ld:%02ld:%02ld",
hours,
mins,
secs);
// mi->duration = ic->duration * av_q2d(stream->time_base);
// std::cout << "video_time : " <<
// (mi->duration / 3600)<< ":" <<
// (mi->duration % 3600) / 60<< ":" <<
// (mi->duration % 60) << std::endl;
// char formatStr[128] = {0,};
// sprintf(formatStr, "%02d:%02d:%02d",
// (mi->duration / 3600),
// ((mi->duration % 3600) / 60),
// (mi->duration % 60));
std::cout << "video_time : " << formatStr;
mi->durationFormatStr = formatStr;
}
else{
printf("audio duration unknown ! \n");
}
mi->width = stream->codecpar->width;
mi->height = stream->codecpar->height;
}
...
...
...
return 0;
}