最近业务需要一个转码接口,这个转码接口将用于各平台(Linux、Android、iOS 等),在开发过程中发现,整个接口在 Linux 上运行良好,但在 Android 端却无法正常转码。
根据自己添加的 Log 发现,在初始化音频编码器时发生了问题。经调试发现,起初是因为直接将 codecId == AAC 的编码器用 avcodec_find_encoder_by_name("libfdk_aac)
写死成用 libfdk_aac 库了,而 Android 端因为缺少 libfdk_aac 这个库而无法正常编码音频。
由于 Linux 上直接 apt-get install
就能安装相关库,但是 Android 端却需要自己在 Linux 环境下载源码,再交叉编译、设置 Android 工具链等一堆复杂操作。同时,由于 Android 端只是一个预览的作用,因此,没必要再去折腾 libfdk_aac 在 Android 平台的库的编译了。直接使用安卓默认自带的 aac 库编码就可以了。
但是后来仔细看了下,代码中还有一句 codec = codec ? codec : avcodec_find_encoder(codecId)
,也就是说,如果通过 name 找不到 libfdk_aac
的话,它也是会通过 codecId 找到 aac 这个编码器的。
我在 Linux 上把 avcodec_find_encoder_by_name("libfdk_aac)
这一句注释掉之后,发现在 Linux 上也无法正常编码音频了,而且报了个来自 FFmpeg 的 bug:Specified sample format s16 is invalid or not supported.
这句话,曾经也遇到过,但一直没仔细看。于是,开始查源码,看这一句是从哪里报出来的。
很清楚地,全 FFmpeg 源码只有一处 “Specified sample format”,通过查看这一句所在的上下文可以知道,FFmpeg 先遍历了 avctx->codec->sample_fmts[i]
,也就是编码器所支持的那些 sample_fmts,如果 avctx->sample_fmt == avctx->codec->sample_fmts[i]
,也就是说,如果用户设置的输入采样点采样格式 avctx->sample_fmt
与当前遍历的编码器所支持的采样点格式 avctx->codec->sample_fmts[i]
匹配,就 break
,进入下一个 if,而这下一个 if 就只判断,当前的 i 所指向的 avctx->codec->sample_fmt[i]
是不是 AV_SAMPLE_FMT_NONE
,如果是,就说明把编码器所支持的所有采样格式都遍历完成了,都没有跟用户设置的输入采样点的采样格式匹配起来。这就报出了 Specified sample format s16 is invalid or not supported. 这个错误。
// libavcodec/utils.c/791line
if (avctx->codec->sample_fmts) {
for (i = 0; avctx->codec->sample_fmts[i] != AV_SAMPLE_FMT_NONE; i++) {
if (avctx->sample_fmt == avctx->codec->sample_fmts[i])
break;
if (avctx->channels == 1 &&
av_get_planar_sample_fmt(avctx->sample_fmt) ==
av_get_planar_sample_fmt(avctx->codec->sample_fmts[i])) {
avctx->sample_fmt = avctx->codec->sample_fmts[i];
break;
}
}
if (avctx->codec->sample_fmts[i] == AV_SAMPLE_FMT_NONE) {
char buf[128];
snprintf(buf, sizeof(buf), "%d", avctx->sample_fmt);
av_log(avctx, AV_LOG_ERROR, "Specified sample format %s is invalid or not supported\n",
(char *)av_x_if_null(av_get_sample_fmt_name(avctx->sample_fmt), buf));
ret = AVERROR(EINVAL);
goto free_and_end;
}
}
因此,设计编码接口时,不能直接拿用户给的采样格式输入到上下文环境(AVCodecContext)。而要判断一下,用户选择的 codecID 在系统上都有哪些支持的编码器,这些支持的编码器都支持哪些采样格式。如果用户给的输入采样格式与编码器支持的采样格式不匹配,则需要报个警告,或者添加个重采样器转一下采样格式。
因此,我对代码进行了些优化,先让采样格式等于编码器支持的第一个格式,然后开始轮询编码器支持的所有格式与用户给的格式进行匹配判断,如有匹配成功,则让采样格式等于用户给的格式,否则,就等于编码器支持的第一个格式。
// pAudioCodec: AVCodec*,由 avcodec_find_encoder(codec_id) 返回。
// pDestAudioInfo:自定义格式,用户输入的音频信息。
AVSampleFormat sf = pAudioCodec->sample_fmts[0];
for (int i = 0; pAudioCodec->sample_fmts[i] != AV_SAMPLE_FMT_NONE; i++) {
if (pAudioCodec->sample_fmts[i] == pDestAudioInfo->sample_format) {
sf = pAudioCodec->sample_fmts[i];
break;
}
}
codec_context_->sample_fmt = sf;