ffmpeg在码流转换上面实在是强大,今天实验了一下把G711音频专成PCM的音频,并最终实验成功。
第一步:寻找解码器,若格式不支持,则无法转码
codec = avcodec_find_decoder(AV_CODEC_ID_PCM_ALAW);
if (!codec) {
fprintf(stderr, "Codec not found\n");
return false;
}
提醒:G711A的解码ID为AV_CODEC_ID_PCM_ALAW
第二步:创建解码上下文,主要保持解码相关的信息
c = avcodec_alloc_context3(codec);
if (!c) {
fprintf(stderr, "Could not allocate audio codec context\n");
return false;
}
c->sample_fmt = sample_fmt;
c->sample_rate = sample_rate;
c->channels = channels;
需要设置3个参数,不然后面打开解码器会失败
sample_fmt:采样格式,标识含义8位,16位等采样
sample_rate:采样频率
channels:采样通道数
第三步:打开解码器
if (avcodec_open2(c, codec, NULL) < 0) {
fprintf(stderr, "Could not open codec\n");
return false;
}
第四步:输入码流
ret = avcodec_send_packet(c, packet);
第五步:输出解码结果
int ret = avcodec_receive_frame(c, frame);
整个过程大约分为这5个步骤。
另外通过研究,有几个重要的关于内存的发现:
由于输入解码的数据格式是AVPacket,而我拿到的数据确是字节流,需要把字节流转换成AVPacket类型。
这里作了如下处理:
packet->size = iSize;
packet->data = (uint8_t *)av_malloc(packet->size);
memcpy(packet->data, pData, iSize);
ret = av_packet_from_data(packet, packet->data, packet->size);
if (ret <0)
{
fprintf(stderr, "av_packet_from_data error \n");
av_free(packet->data);
return;
}
ret = avcodec_send_packet(c, packet);
av_packet_unref(packet);
通过函数av_packet_from_data生产了AVPacket类型数据,但是需要注意在使用完之后,需要调用av_packet_unref进行释放内存,不然会存在内存泄漏,有人会问上面的内存是通过av_malcoc生成的,我们还需要释放吗?我通过实验发现调用av_packet_unref之后,就不需要再调用av_free来进行释放了,ffmpeg好像自己已经释放了。
另外关于解码出来的音频,需要注意他的存放再uint8_t *data[AV_NUM_DATA_POINTERS];里面的,这是一个数组,每个通道一维。由于pcm保持多通道根据采样率进行顺序保持,所以保持位pcm需要处理一下:
int iCopyPos = 0;
for(int i = 0; i < frame->nb_samples; i++)
{
for (int ch = 0; ch < c->channels; ch++)
{
memcpy(m_pOutData + iCopyPos,frame->data[ch] + data_size * i, data_size);
iCopyPos = iCopyPos + data_size;
}
}
最后,为了便于大家学习和交流,我采用vs2017开发,上传示例如下连接: