最近在测试通过rtsp采集海康的摄像头,以前海康的摄像头采用g711音频,没出现什么问题,这次的设备升级成了aac音频,结果使用ffmpeg出现音频格式不能识别的问题,报什么Audio object type 0的错误,查看发现是解码器的objecttype成了0造成的,于是查看源码。
跟踪发现打开rtsp时发来的sdp信息中音频的信息如下:
a=rtpmap:104 mpeg4-generic/16000/1
a=fmtp:104 profile-level-id=15; streamtype=5; mode=AAC-hbr; config=0400;SizeLength=13; IndexLength=3; IndexDeltaLength=3; Profile=1;
a=Media_header:MEDIAINFO=494D4B48010100000400010001200110803E000000FA", '0' <repeats 36 times>, ";
a=appversion:1.0
其中音频的Profile与objecttype是对应的,而音频采样率和声道数也已经给出了,但在程序中调试时该值却始终不对。
到网上查资料得知,config参数实际上包含了objecttype和采样率及声道数,于是采用网上给出的公式
audioSpecificConfig[0] = (audioObjectType<<3) | (samplerateindex>>1);
audioSpecificConfig[1] = (samplerateindex<<7) | (channels<<3);
算了一下,发现海康摄像头给出的sdp中的config和profile、samplerateindex、channels对不上,于是对ffmpeg的源码做出了修改:
要修改的文件是rtpdec_mpeg4.c
首先需要定义的取值,于是定义了
const int avpriv_aac_sample_rates[16] = {
96000, 88200, 64000, 48000, 44100, 32000,
24000, 22050, 16000, 12000, 11025, 8000, 7350
};
然后profile在PayloadContext中没有定义,ffmpeg源码没有对此值进行分析,于是在PayloadContext的定义中添加
char* profile;
此处添加该值为指针,并修改
static PayloadContext *new_context(void)
{
PayloadContext * newval = av_mallocz(sizeof(PayloadContext));
newval->profile = NULL;
return newval;
}
方便在后面sdp有没有出现过profile这个项。
接下来在static const AttrNameMap attr_names[]的定义中添加
{ "Profile", ATTR_NAME_TYPE_STR,
offsetof(PayloadContext, profile) },
以方便在后面分析sdp取得profile值。
修改
static int parse_fmtp(AVFormatContext *s,
AVStream *stream, PayloadContext *data,
char *attr, char *value)
{
AVCodecContext *codec = stream->codec;
int res, i;
if (!strcmp(attr, "config")) {
if (data->profile == NULL)
{
res = parse_fmtp_config(codec, value);
if (res < 0)
return res;
}
}
if (codec->codec_id == AV_CODEC_ID_AAC) {
/* Looking for a known attribute */
for (i = 0; attr_names[i].str; ++i) {
if (!av_strcasecmp(attr, attr_names[i].str)) {
if (attr_names[i].type == ATTR_NAME_TYPE_INT) {
*(int *)((char *)data+
attr_names[i].offset) = atoi(value);
} else if (attr_names[i].type == ATTR_NAME_TYPE_STR)
*(char **)((char *)data+
attr_names[i].offset) = av_strdup(value);
}
if ((!strcmp(attr, "Profile"))&&(data->profile != NULL))
{
PutBitContext pb;
av_free(codec->extradata);
if (ff_alloc_extradata(codec, 2))
return AVERROR(ENOMEM);
init_put_bits(&pb, codec->extradata, codec->extradata_size*8);
codec->profile = atoi(data->profile);
put_bits(&pb, 5, codec->profile + 1);
for (int i = 0; i < 16; i++)
if (codec->sample_rate == avpriv_aac_sample_rates[i])
{
put_bits(&pb, 4, i);
break;
}
put_bits(&pb, 4, codec->channels);
flush_put_bits(&pb);
}
}
}
return 0;
}
修改的逻辑是,当sdp中没有profile时,将config写入到codec的extradata中;如果sdp中有profile,那么config将不会写入,已写入的也会被profile、samplerateindex、channels的值给替换掉。这样就保证了在没有profile时采用sdp的config,有则采用profile。