Ffmpeg视频开发教程(五)————2018最新版ffmpeg开发包(4.0)实现pcm数据编码为mpeg audio音频文件(mp2)
网上有很多关于ffmpeg编码音频的文章,但多是基于老版本。而且很多缺胳膊少腿,注释也不够详细。本文基于最新的2018年的ffmpeg 4.0开发包实现音频pcm数据的编码,注释非常详细。比如下面的注释:
//文件的采样率是44100, 格式是AV_SAMPLE_FMT_S16, 有两个通道,即每个通道是16位有符号数据,存储方式为C1C2C1C2.......。其中C1表示第一个通道的16位数,C2表示第二个通道的16位数
//注意后面的编码器的采样个是必须和pcm的格式一致
给出了实现程序一些非常重要的细节。
本文之所以编码为mp2,是因为mp2编码比较简单,能够把一个问题讲清楚。如果选择mp3,那么因为mp3编码器不直接支持AV_SAMPLE_FMT_S16格式的pcm数据,涉及到重采样等其它知识,略微复杂。
本文使用的测试用的pcm文件的下载地址为: https://download.youkuaiyun.com/download/zhangamxqun/10440053
压缩效果非常明显,压缩前的pcm文件40多兆,编码后只有2兆多。
网上有很多关于ffmpeg编码音频的文章,但多是基于老版本。而且很多缺胳膊少腿,注释也不够详细。本文基于最新的2018年的ffmpeg 4.0开发包实现音频pcm数据的编码,注释非常详细。比如下面的注释:
//文件的采样率是44100, 格式是AV_SAMPLE_FMT_S16, 有两个通道,即每个通道是16位有符号数据,存储方式为C1C2C1C2.......。其中C1表示第一个通道的16位数,C2表示第二个通道的16位数
//注意后面的编码器的采样个是必须和pcm的格式一致
给出了实现程序一些非常重要的细节。
本文之所以编码为mp2,是因为mp2编码比较简单,能够把一个问题讲清楚。如果选择mp3,那么因为mp3编码器不直接支持AV_SAMPLE_FMT_S16格式的pcm数据,涉及到重采样等其它知识,略微复杂。
本文使用的测试用的pcm文件的下载地址为: https://download.youkuaiyun.com/download/zhangamxqun/10440053
压缩效果非常明显,压缩前的pcm文件40多兆,编码后只有2兆多。
本文程序的环境搭建参考我的第一篇FFMPEG教程:https://blog.youkuaiyun.com/zhangamxqun/article/details/80304494
下面就是核心代码和最详细的注释:
/**
实现2018版本FFMPEG将pcm音频数据编码为Mepeg Audio文件(mp2),作者自己测试正确可用
作者:明天继续
使用的ffmpeg版本:ffmpeg-20180508-293a6e8-win32
开发工具:vs2012
**/
#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
extern "C" {
#include <libavutil/frame.h>
#include <libavutil/mem.h>
#include <libavcodec/avcodec.h>
#include <libavutil/channel_layout.h>
#include <libavutil/common.h>
#include <libavformat/avformat.h>
#include <libavutil/samplefmt.h>
}
#pragma comment(lib,"../../ffmpeg-20180508-293a6e8-win32-dev/lib/avcodec.lib")
#pragma comment(lib,"../../ffmpeg-20180508-293a6e8-win32-dev/lib/avformat.lib")
#pragma comment(lib,"../../ffmpeg-20180508-293a6e8-win32-dev/lib/avfilter.lib")
#pragma comment(lib,"../../ffmpeg-20180508-293a6e8-win32-dev/lib/avutil.lib")
#if _MSC_VER
#define snprintf _snprintf_s
#define PRIx64 "I64x"
#define PRIX64 "I64X"
#endif
/* 检查编码器是否支持指定的采样格式 */
static int check_sample_fmt(const AVCodec *codec, enum AVSampleFormat sample_fmt)
{
const enum AVSampleFormat *p = codec->sample_fmts;
while (*p != AV_SAMPLE_FMT_NONE) {
if (*p == sample_fmt)
return 1;
p++;
}
return 0;
}
/* 选择最高的采样率 */
static int select_sample_rate(const AVCodec *codec)
{
const int *p;
int best_samplerate = 0;
if (!codec->supported_samplerates)
return 44100;
p = codec->supported_samplerates;
while (*p) {
if (!best_samplerate || abs(44100 - *p) < abs(44100 - best_samplerate))
best_samplerate = *p;
p++;
}
return best_samplerate;
}
/* 最大通道数的层 */
static int select_channel_layout(const AVCodec *codec)
{
const uint64_t *p;
uint64_t best_ch_layout = 0;
int best_nb_channels = 0;
if (!codec->channel_layouts)
return AV_CH_LAYOUT_STEREO;
p = codec->channel_layouts;
while (*p) {
int nb_channels = av_get_channel_layout_nb_channels(*p);
if (nb_channels > best_nb_channels) {
best_ch_layout = *p;
best_nb_channels = nb_channels;
}
p++;
}
return best_ch_layout;
}
static void encode(AVCodecContext *ctx, AVFrame *frame, AVPacket *pkt,
FILE *output)
{
int ret;
/* 编码frame */
ret = avcodec_send_frame(ctx, frame);
if (ret < 0) {
fprintf(stderr, "Error sending the frame to the encoder\n");
exit(1);
}
/* 读取编码出的数据包,通常不止一个 */
while (ret >= 0) {
ret = avcodec_receive_packet(ctx, pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
return;
else if (ret < 0) {
fprintf(stderr, "Error encoding audio frame\n");
exit(1);
}
fwrite(pkt->data, 1, pkt->size, output);
av_packet_unref(pkt);
}
}
int _tmain(int argc, _TCHAR* argvec[])
{
//输入原始的pcm数据文件
//文件的采样率是44100, 格式是AV_SAMPLE_FMT_S16, 有两个通道,即每个通道是16位有符号数据,存储方式为C1C2C1C2.......。其中C1表示第一个通道的16位数,C2表示第二个通道的16位数
//注意后面的编码器的采样个是必须和pcm的格式一致
char *intput_file = "test.pcm";
//输出编码后的音频文件,为MPEG Audio 文件
char *output_file = "out.mp2";
av_register_all();
//编码器
const AVCodec *codec;
//编码器上下文
AVCodecContext *c= NULL;
//pcm数据帧
AVFrame *frame;
//编码后的mp2数据包
AVPacket *pkt;
int ret;
//输出文件
FILE *f;
/* 找到MP2的编码器 */
codec = avcodec_find_encoder(AV_CODEC_ID_MP2);
if (!codec) {
fprintf(stderr, "Codec not found\n");
exit(1);
}
//生成编码器上下文
c = avcodec_alloc_context3(codec);
if (!c) {
fprintf(stderr, "Could not allocate audio codec context\n");
exit(1);
}
/* 设置编码器上下文的bit率 */
c->bit_rate = 64000;
/* 检查编码器支持我们输入的pcm数据格式,如果不支持需要进行重采样,后续讨论如何重采样 */
c->sample_fmt = AV_SAMPLE_FMT_S16;
if (!check_sample_fmt(codec, c->sample_fmt)) {
fprintf(stderr, "Encoder does not support sample format %s",
av_get_sample_fmt_name(c->sample_fmt));
exit(1);
}
/* 设置编码器的必要参数 */
c->sample_rate = select_sample_rate(codec);
c->channel_layout = select_channel_layout(codec);
c->channels = av_get_channel_layout_nb_channels(c->channel_layout);
/* 打开编码器,打开后,会完善一些参数,比如编码器上下文的frame_size */
if (avcodec_open2(c, codec, NULL) < 0) {
fprintf(stderr, "Could not open codec\n");
exit(1);
}
//打开输出文件
f = fopen(output_file, "wb");
if (!f) {
fprintf(stderr, "Could not open %s\n", output_file);
exit(1);
}
/* 初始化mp2编码结果数据的存储包 */
pkt = av_packet_alloc();
if (!pkt) {
fprintf(stderr, "could not allocate the packet\n");
exit(1);
}
/* 初始化存放pcm数据的frame */
frame = av_frame_alloc();
if (!frame) {
fprintf(stderr, "Could not allocate audio frame\n");
exit(1);
}
//设置pcm数据frame的一些参数
//frame size 是数据中有多少个样本,每个样本包含两个通道各一个数据,每个数据占16位2个字节。所以frame得实际大小是 frame_size * sizeof(uint16_t)*2
frame->nb_samples = c->frame_size;
frame->format = c->sample_fmt;
frame->channel_layout = c->channel_layout;
/* 分配frame的内存空间 */
ret = av_frame_get_buffer(frame, 0);
if (ret < 0) {
fprintf(stderr, "Could not allocate audio data buffers\n");
exit(1);
}
FILE * inputfile = fopen(intput_file, "rb");
unsigned int readsize = 0;
while (!feof(inputfile)) {
ret = av_frame_make_writable(frame);
if (ret < 0)
exit(1);
/* 从pcm文件读取数据 */
readsize = fread(frame->data[0], sizeof(uint16_t)*2, c->frame_size, inputfile);
if (!readsize)
break;
c->frame_size = readsize;
encode(c, frame, pkt, f);
}
fclose(inputfile);
/* 处理最后的残余数据*/
encode(c, NULL, pkt, f);
fclose(f);
av_frame_free(&frame);
av_packet_free(&pkt);
avcodec_free_context(&c);
return 0;
}