这个问题是这样产生的,一同事反应会随机出现ijk获取到的aac文件的duration不准,发来一看,确实不准,在AE或者系统mediaplayer中得到的都是8.4秒(准确时间是MtkAACExtractor: ADTS: duration = 8448000 us),ijk得到的是9.3秒,在播放的时候,在8秒的时候流就结束了,放到编译的ffmpeg中,一看也是9.3秒。

1.分析问题

下面开始分析这个问题,命令行看下这个文件,ffmpeg中获取到的确实是9.3秒

仔细看下红色箭头所指,这个意思是获取到的duration是根据比特率计算的,可能不准确。这种获取音视频info有问题的我们一般可以从avformat_find_stream_info函数开始分析。

这里直接从log开始看,waring出现出现在utils.c/libavformat下

static void estimate_timings_from_bit_rate(AVFormatContext *ic)
{int64_t filesize, duration;int i, show_warning = 0;AVStream *st;av_log(ic, AV_LOG_WARNING,"hxk-->ic->bit_rate:%lld\n",ic->bit_rate);//这里从log可以看到,bitrate也没获取到,bitrate = 0/* if bit_rate is already set, we believe it */if (ic->bit_rate <= 0) {int64_t bit_rate = 0;for (i = 0; i < ic->nb_streams; i++) {st = ic->streams[i];if (st->codecpar->bit_rate <= 0 && st->internal->avctx->bit_rate > 0)st->codecpar->bit_rate = st->internal->avctx->bit_rate;if (st->codecpar->bit_rate > 0) {if (INT64_MAX - st->codecpar->bit_rate < bit_rate) {bit_rate = 0;break;}bit_rate += st->codecpar->bit_rate;} else if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && st->codec_info_nb_frames > 1) {// If we have a videostream with packets but without a bitrate// then consider the sum not knownbit_rate = 0;break;}}//这里算出来一个bitrateic->bit_rate = bit_rate;av_log(ic, AV_LOG_WARNING,"hxk-->ic->bit_rate:%lld\n",ic->bit_rate);}//从log中可以看到,这里的duration也是0/* if duration is already set, we believe it */av_log(ic, AV_LOG_WARNING,"hxk-->ic->duration:%lld\n",ic->duration);if (ic->duration == AV_NOPTS_VALUE &&ic->bit_rate != 0) {filesize = ic->pb ? avio_size(ic->pb) : 0;av_log(ic, AV_LOG_WARNING,"hxk-->ic->filesize:%lld\n",filesize);if (filesize > ic->internal->data_offset) {filesize -= ic->internal->data_offset;for (i = 0; i < ic->nb_streams; i++) {st      = ic->streams[i];if (   st->time_base.num <= INT64_MAX / ic->bit_rate&& st->duration == AV_NOPTS_VALUE) {//这里根据文件字节*8 /比特率来计算duration,这里cbr这样计算可以计算,但是如果vbr(码率动态)的话就有问题了duration = av_rescale(8 * filesize, st->time_base.den,ic->bit_rate *(int64_t) st->time_base.num);//获取到的duration就不准确了st->duration = duration;show_warning = 1;}}}}if (show_warning)av_log(ic, AV_LOG_WARNING,"Estimating duration from bitrate, this may be inaccurate\n");
}

调用上面这个函数的地方是utils.c/libavofrmat:

static void estimate_timings(AVFormatContext *ic, int64_t old_offset)
{int64_t file_size;/* get the file size, if possible */if (ic->iformat->flags & AVFMT_NOFILE) {file_size = 0;} else {file_size = avio_size(ic->pb);file_size = FFMAX(0, file_size);}av_log(ic, AV_LOG_WARNING, "hxk->ic->iformat->name:%s\n", ic->iformat->name);av_log(ic, AV_LOG_WARNING, "hxk->file_size:%lld\n", file_size);av_log(ic, AV_LOG_WARNING, "hxk->ic->pb->seekable:%d\n", ic->pb->seekable);if ((!strcmp(ic->iformat->name, "mpeg") ||!strcmp(ic->iformat->name, "mpegts")) &&file_size && (ic->pb->seekable & AVIO_SEEKABLE_NORMAL)) {/* get accurate estimate from the PTSes */estimate_timings_from_pts(ic, old_offset);ic->duration_estimation_method = AVFMT_DURATION_FROM_PTS;} else if (has_duration(ic)) {//如果在demuxer中获取到duration了/* at least one component has timings - we use them for all* the components */fill_all_stream_timings(ic);ic->duration_estimation_method = AVFMT_DURATION_FROM_STREAM;} else {//这个文件没有获取到duration,所以走的是这里/* less precise: use bitrate info */estimate_timings_from_bit_rate(ic);ic->duration_estimation_method = AVFMT_DURATION_FROM_BITRATE;}update_stream_timings(ic);{int i;AVStream av_unused *st;for (i = 0; i < ic->nb_streams; i++) {st = ic->streams[i];av_log(ic, AV_LOG_TRACE, "stream %d: start_time: %0.3f duration: %0.3f\n", i,(double) st->start_time * av_q2d(st->time_base),(double) st->duration   * av_q2d(st->time_base));}av_log(ic, AV_LOG_TRACE,"format: start_time: %0.3f duration: %0.3f bitrate=%"PRId64" kb/s\n",(double) ic->start_time / AV_TIME_BASE,(double) ic->duration   / AV_TIME_BASE,(int64_t)ic->bit_rate / 1000);}
}

调用上面这个方法是在avformat_find_stream_info/utils.c/libavformat函数中。

2.解决问题

原因已经知道了,那么可以如何解决这个问题呢?
aac的duration可以如何获取呢?
我们看下android系统中libstagefright框架中aacextractore的实现

AACExtractor.cpp/libstagefrgiht

AACExtractor::AACExtractor(const sp<DataSource> &source, const sp<AMessage> &_meta): mDataSource(source),mInitCheck(NO_INIT),mFrameDurationUs(0) {sp<AMessage> meta = _meta;if (meta == NULL) {String8 mimeType;float confidence;sp<AMessage> _meta;if (!SniffAAC(mDataSource, &mimeType, &confidence, &meta)) {return;}}int64_t offset;CHECK(meta->findInt64("offset", &offset));uint8_t profile, sf_index, channel, header[2];if (mDataSource->readAt(offset + 2, &header, 2) < 2) {return;}
//获取profileprofile = (header[0] >> 6) & 0x3;
//获取采样索引sf_index = (header[0] >> 2) & 0xf;
//获取采样率uint32_t sr = get_sample_rate(sf_index);if (sr == 0) {return;}
//通道channel = (header[0] & 0x1) << 2 | (header[1] >> 6);mMeta = MakeAACCodecSpecificData(profile, sf_index, channel);off64_t streamSize, numFrames = 0;size_t frameSize = 0;int64_t duration = 0;
//获取文件大小if (mDataSource->getSize(&streamSize) == OK) {while (offset < streamSize) {//获取adts每一帧大小if ((frameSize = getAdtsFrameLength(source, offset, NULL)) == 0) {return;}mOffsetVector.push(offset);offset += frameSize;//偏移加加numFrames ++;//计算帧数目}
//***************重点看下这里,这里在下面分析aac文件格式的时候会讲解细致一点*************// Round up and get the durationmFrameDurationUs = (1024 * 1000000ll + (sr - 1)) / sr;duration = numFrames * mFrameDurationUs;//总帧数x一个AAC音频帧的播放时间mMeta->setInt64(kKeyDuration, duration);}mInitCheck = OK;
}

我们再看下getAdtsFrameLength/AACExtractor.cpp/libstagefrgiht函数,这个函数其实就是根据adts头来计算出每一个framesize的大小的

static size_t getAdtsFrameLength(const sp<DataSource> &source, off64_t offset, size_t* headerSize) {
//CRCconst size_t kAdtsHeaderLengthNoCrc = 7;const size_t kAdtsHeaderLengthWithCrc = 9;size_t frameSize = 0;
//同步字uint8_t syncword[2];if (source->readAt(offset, &syncword, 2) != 2) {return 0;}if ((syncword[0] != 0xff) || ((syncword[1] & 0xf6) != 0xf0)) {return 0;}
//0没有crc,1有crcuint8_t protectionAbsent;if (source->readAt(offset + 1, &protectionAbsent, 1) < 1) {return 0;}protectionAbsent &= 0x1;uint8_t header[3];if (source->readAt(offset + 3, &header, 3) < 3) {return 0;}
//获取framesize的大小frameSize = (header[0] & 0x3) << 11 | header[1] << 3 | header[2] >> 5;// protectionAbsent is 0 if there is CRCsize_t headSize = protectionAbsent ? kAdtsHeaderLengthNoCrc : kAdtsHeaderLengthWithCrc;if (headSize > frameSize) {return 0;}if (headerSize != NULL) {*headerSize = headSize;}return frameSize;
}

上面的实现原理就是根据一个AAC原始帧包含一段时间内1024个采样及相关数据。一个AAC音频帧的播放时间=一个AAC帧对应的采样样本的个数/采样率。所以aac音频文件总时间t=总帧数x一个AAC音频帧的播放时间

下面看一下aac的demuxer,在aacdec.c/libavformat下,发现里面连对aidf头的处理都没有,这个先不管了。

AAC格式:

下面我们先简单看下aac的格式:

详细的AAC格式参考下这篇文章吧,实在懒得写。

AAC文件格式与音频文件时长计算

解决问题

下面我们看下ffmpeg中这个格式的demuxer,这个文件封装格式raw ADTS AAC,下面我们看下aacdec.c/libavformat

修改aacdec.c文件,新加函数

//add by hxk
//获取adts frame的帧长
static int getAdtsFrameLength(AVFormatContext *s,int64_t offset,int* headerSize)
{int64_t filesize, position = avio_tell(s->pb);  filesize = avio_size(s->pb);//av_log(NULL, AV_LOG_WARNING, "hxk->getAdtsFrameLength.filesize:%d\n",filesize);const int kAdtsHeaderLengthNoCrc = 7;const int kAdtsHeaderLengthWithCrc = 9;int frameSize = 0;uint8_t syncword[2];avio_seek(s->pb, offset, SEEK_SET);//读取同步字if(avio_read(s->pb,&syncword, 2)!= 2){return 0;}if ((syncword[0] != 0xff) || ((syncword[1] & 0xf6) != 0xf0)) {return 0;}uint8_t protectionAbsent;avio_seek(s->pb, offset+1, SEEK_SET);//读取protectionAbsentif (avio_read(s->pb, &protectionAbsent, 1) < 1) {return 0;}protectionAbsent &= 0x1;uint8_t header[3];
//读取headeravio_seek(s->pb, offset+3, SEEK_SET);if (avio_read(s->pb, &header, 3) < 3) {return 0;}//获取framesizeframeSize = (header[0] & 0x3) << 11 | header[1] << 3 | header[2] >> 5;// protectionAbsent is 0 if there is CRCint headSize = protectionAbsent ? kAdtsHeaderLengthNoCrc : kAdtsHeaderLengthWithCrc;if (headSize > frameSize) {return 0;}if (headerSize != NULL) {*headerSize = headSize;}return frameSize;
}
//根据采样率下标获取采样率
static uint32_t get_sample_rate(const uint8_t sf_index)
{static const uint32_t sample_rates[] ={96000, 88200, 64000, 48000, 44100, 32000,24000, 22050, 16000, 12000, 11025, 8000};if (sf_index < sizeof(sample_rates) / sizeof(sample_rates[0])) {return sample_rates[sf_index];}return 0;
}//add end

修改adts_aac_read_header函数

static int adts_aac_read_header(AVFormatContext *s)
{av_log(NULL, AV_LOG_WARNING, "hxk->adts_aac_read_header!\n");AVStream *st;uint16_t state;st = avformat_new_stream(s, NULL);if (!st)return AVERROR(ENOMEM);st->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;st->codecpar->codec_id   = s->iformat->raw_codec_id;st->need_parsing         = AVSTREAM_PARSE_FULL_RAW;ff_id3v1_read(s);if ((s->pb->seekable & AVIO_SEEKABLE_NORMAL) &&!av_dict_get(s->metadata, "", NULL, AV_DICT_IGNORE_SUFFIX)) {int64_t cur = avio_tell(s->pb);ff_ape_parse_tag(s);avio_seek(s->pb, cur, SEEK_SET);}// skip data until the first ADTS frame is foundstate = avio_r8(s->pb);while (!avio_feof(s->pb) && avio_tell(s->pb) < s->probesize) {state = (state << 8) | avio_r8(s->pb);if ((state >> 4) != 0xFFF)continue;avio_seek(s->pb, -2, SEEK_CUR);break;}if ((state >> 4) != 0xFFF)return AVERROR_INVALIDDATA;// LCM of all possible ADTS sample rates//   avpriv_set_pts_info(st, 64, 1, 28224000);
//add by hxk
#if  1//句柄指回起点avio_seek(s->pb, 0, SEEK_SET);uint8_t profile, sf_index, channel, header[2];//文件指针移动到文件起点前2个字节avio_seek(s->pb, 2, SEEK_SET);if (avio_read(s->pb,&header, 2) < 2) {av_log(NULL, AV_LOG_ERROR, "avio_read header error!\n");return 0;}int64_t offset = 0;//获取profileprofile = (header[0] >> 6) & 0x3;st->codecpar->profile = profile;//av_log(NULL, AV_LOG_WARNING, "hxk->profile:%d!\n",profile);sf_index = (header[0] >> 2) & 0xf;//获取采样率uint32_t sr = get_sample_rate(sf_index);//av_log(NULL, AV_LOG_WARNING, "hxk->samplerate:%d!\n",sr);if (sr == 0) {av_log(NULL, AV_LOG_ERROR, "avio_read read sampletare error!\n");return 0;}//赋值给codec参数st->codecpar->sample_rate = sr;//获取通道channel = (header[0] & 0x1) << 2 | (header[1] >> 6);if (channel == 0) {av_log(NULL, AV_LOG_ERROR, "adts_aac_read_header read channel error!\n");return 0;}//赋值给codec 参数st->codecpar->channels = channel;//av_log(NULL, AV_LOG_WARNING, "hxk->channel:%d!\n",channel);sf_index = (header[0] >> 2) & 0xf;int frameSize = 0;int64_t mFrameDurationUs = 0;int64_t duration = 0;//采样率赋值给codecst->codecpar->sample_rate = sr;int64_t streamSize, numFrames = 0;avpriv_set_pts_info(st, 64, 1, st->codecpar->sample_rate);//获取文件大小streamSize =  avio_size(s->pb);// av_log(NULL, AV_LOG_WARNING, "hxk->streamSize:%d!\n",streamSize);if (streamSize > 0) {while (offset < streamSize) {if ((frameSize = getAdtsFrameLength(s, offset, NULL)) == 0) {return 0;}offset += frameSize;//偏移加加numFrames ++;//帧数加加,获取总帧数//    av_log(NULL, AV_LOG_WARNING, "hxk->frameSize:%d!\n",frameSize);}// av_log(NULL, AV_LOG_WARNING, "hxk->numFrames:%lld!\n",numFrames);// Round up and get the duration,计算每一帧时间mFrameDurationUs = (1024 * 1000000ll + (sr - 1)) / sr;duration = numFrames * mFrameDurationUs;//us//时间基转换avstream的,us单位(AV_TIME_BASE_Q)转avstream的时间基duration = av_rescale_q(duration,AV_TIME_BASE_Q, st->time_base);st->duration = duration;//   av_log(NULL, AV_LOG_WARNING, "hxk->duration:%d!\n",duration);}#endif
//add endreturn 0;
}

这样在demuxer中就获得了duration,在上面的estimate_timings函数中就直接走has_duration这个判断了,得到的duration也是比较准确的了。

运行一下修改后的代码,从下图可以看到时间已经改变了,和android中MtkAACExtractor获取的duration是一样的了。

后续

正满心欢喜解决了问题后,把改动的代码移植到ijk上的时候,发现不能播放,没报任何错误,文件info读取都是正确的,seek一下的时候报了这么一行错误

IJKMEDIA: /storage/emulated/0/3ee807175fc2488d8264ac014ccf55ff.aac: error while seeking

原来忘记把句柄置回去了
修改如下:

static int adts_aac_read_header(AVFormatContext *s)
{AVStream *st;uint16_t state;st = avformat_new_stream(s, NULL);if (!st)return AVERROR(ENOMEM);st->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;st->codecpar->codec_id   = s->iformat->raw_codec_id;st->need_parsing         = AVSTREAM_PARSE_FULL_RAW;ff_id3v1_read(s);if ((s->pb->seekable & AVIO_SEEKABLE_NORMAL) &&!av_dict_get(s->metadata, "", NULL, AV_DICT_IGNORE_SUFFIX)) {int64_t cur = avio_tell(s->pb);ff_ape_parse_tag(s);avio_seek(s->pb, cur, SEEK_SET);}// skip data until the first ADTS frame is foundstate = avio_r8(s->pb);while (!avio_feof(s->pb) && avio_tell(s->pb) < s->probesize) {state = (state << 8) | avio_r8(s->pb);if ((state >> 4) != 0xFFF)continue;avio_seek(s->pb, -2, SEEK_CUR);break;}if ((state >> 4) != 0xFFF)return AVERROR_INVALIDDATA;// LCM of all possible ADTS sample rates// avpriv_set_pts_info(st, 64, 1, 28224000);//add by hxk
#if  1avio_seek(s->pb, 0, SEEK_SET);uint8_t profile, sf_index, channel, header[2];avio_seek(s->pb, 2, SEEK_SET);if (avio_read(s->pb,&header, 2) < 2) {av_log(NULL, AV_LOG_ERROR, "avio_read header error!\n");return 0;}int64_t offset = 0;profile = (header[0] >> 6) & 0x3;st->codecpar->profile = profile;sf_index = (header[0] >> 2) & 0xf;uint32_t sr = get_sample_rate(sf_index);if (sr == 0) {av_log(NULL, AV_LOG_ERROR, "adts_aac_read_header read sampletare error!\n");return 0;}st->codecpar->sample_rate = sr;channel = (header[0] & 0x1) << 2 | (header[1] >> 6);if(channel == 0) {av_log(NULL, AV_LOG_ERROR, "adts_aac_read_header read channel error!\n");return 0;}st->codecpar->channels = channel;sf_index = (header[0] >> 2) & 0xf;int frameSize = 0;int64_t mFrameDurationUs = 0;int64_t duration = 0;st->codecpar->sample_rate = sr;int64_t streamSize, numFrames = 0;avpriv_set_pts_info(st, 64, 1, st->codecpar->sample_rate);streamSize =  avio_size(s->pb);//av_log(NULL, AV_LOG_WARNING, "hxk->streamSize:%d!\n",streamSize);if (streamSize > 0) {while (offset < streamSize) {if ((frameSize = getAdtsFrameLength(s, offset, NULL)) == 0) {return 0;}offset += frameSize;numFrames ++;//av_log(NULL, AV_LOG_WARNING, "hxk->frameSize:%d!\n",frameSize);}// av_log(NULL, AV_LOG_WARNING, "hxk->numFrames:%lld!\n",numFrames);// Round up and get the durationmFrameDurationUs = (1024 * 1000000ll + (sr - 1)) / sr;duration = numFrames * mFrameDurationUs;//usduration = av_rescale_q(duration,AV_TIME_BASE_Q, st->time_base);st->duration = duration;//av_log(NULL, AV_LOG_WARNING, "hxk->duration:%d!\n",duration);}//置回句柄avio_seek(s->pb, 0, SEEK_SET);#endif//add endreturn 0;
}

嗯,可以获取正确的时间来正常播放了。

ffmpeg系列-解决ffmpeg获取aac音频文件duration不准相关推荐

  1. 解决ffmpeg获取AAC音频文件duration不准

    最近测试提出了一个bug,ijk获取到的aac文件的duration不准,发来一看,确实不准,在AE或者系统mediaplayer中得到的都是3m48s(准确时间是MMParserExtractor: ...

  2. FFMPEG操作流数据-获取aac音频文件

    多媒体文件是一个容器,在容器里有很多的流(Stream/Track[翻译:轨]),如视频流.音频流.字幕流等. 常见操作 解复用 获取流(AVStream) 读数据包(AVPacket) 解码/编码( ...

  3. 用Python的mutagen模块获取MP3音频文件的时长

    工作中碰到的需求,今天下午虽然解决了,但是不太简洁(本质上还是用的mutagen...),回家查了查试了试,用mutagen可以很容易获取MP3音频文件的长度, 一共三行代码 from mutagen ...

  4. Android MediaCodec硬解码AAC音频文件并播放

    在这里简单介绍一下,如何利用Android MediaCodec解码AAC音频文件或者实时AAC音频帧并通过AudioTrack来播放.主要的思路就是从文件或者网络获取一帧帧的AAC的数据,送入解码器 ...

  5. Android MediaCodec硬解码AAC音频文件(实时AAC音频帧)并播放

    转载请注明出处:http://blog.csdn.net/a512337862/article/details/72629755 今天在这里简单介绍一下,如何利用android MediaCodec解 ...

  6. 如何制作自己想要的AAC音频文件

    本文只是介绍我制作AAC音频文件的整个过程,只作为参考,大家如果有更好的方法,可以不使用此方法. 1.在微信小程序里搜索 语音朗读助手,并点击打开 2.打开小程序后,点击输入文件或链接,将你需要转换成 ...

  7. Android获取mp3音频文件播放总时长

    Android获取mp3音频文件播放总时长 一般是需要文件的绝对路径,需要读取文件,转成媒体类解析,最好获取播放时长,然后关闭资源. /** * 获取音频文件的总时长大小 * * @param fil ...

  8. Python获取mp3音频文件时长方法汇总

    '''pymediainfo: pip3 install pymediainfo 版本:5.1.0不支持网络音频 ''' class pymediainfoTest():@classmethoddef ...

  9. Java 获取opus 音频文件时长

    当时为了获取时长花费好长时间,所以现在写出这文章以免后面有遇到该问题不止如何解决花费太长时间.话不多说,上代码 需要的依赖包有 <!-- https://mvnrepository.com/ar ...

最新文章

  1. 走进科学-小菌株大作为—枯草芽孢杆菌替代畜牧业抗生素添加
  2. datagrid中巧用loadFilter对数据整形
  3. 插入排序Insertion sort 2
  4. win8.1下jdk的安装和环境变量的配置 eclipse的安装和汉化
  5. js foreach 跳出循环_VUE.js
  6. Spring基础——在 Spring Config 文件中基于 XML 的 Bean 的自动装配
  7. 多维数组做参数,内存可以看做是线性的
  8. vss中项目与服务器断开绑定之后进行重新绑定得方法
  9. java 协变 逆变_JAVA中的协变与逆变
  10. win10虚拟显示器开发
  11. banner图片不拉伸、全屏宽、居中显示的方法
  12. Android Studio方法总数超过64K报错Error:The number of method references in a .dex file cannot exceed 64K.
  13. Xtract or Ucfyber的最条理的整理入门级教程
  14. 【黑马程序员西安中心】 css布局
  15. 解决ONENOTE for WIN10同步问题,E000006B同步错误
  16. bcache / 如何使用bcache构建LVM,软RAID / 如何优化bcache
  17. 统计表中百分比的表示方法
  18. ssm基于微信小程序的校园跑腿系统——计算机毕业设计
  19. QQ空间小秘书 V1.10 Beta2 正式发布~~ 天空原创软件
  20. Android利用universal-image-loader异步加载大量图片完整示例

热门文章

  1. 51Nod-1001 数组中和等于K的数对【排序+二分查找】
  2. Graphviz样例之集群流程图
  3. 编程理论 —— 计算图框架
  4. windows 用户管理
  5. 经济学的概念、术语与常识
  6. for 循环 and while 循环(二)
  7. Python 标准库 —— xml
  8. fire.php,php代码调试利器firephp安装与使用方法分析
  9. mysql 保存时间报错_JPA在MySQL数据库中保存错误的日期
  10. 从全职高手开始的系统_全职高手8年登上巅峰荣耀,阅文做对了什么?