Linux下FFMPEG--H264--编码&&解码的C实现与相关原理详解
FFMPEG是很强大的一套视频音频处理库,不过,强大的功能一般免不了复杂的实现,或者更加现实地说,“麻烦”的部署和使用流程
关于“FFMPEG怎么部署”这事就放在另一篇文章啦,下面入正题。。
编码encoder模块和解码decoder模块都有init初始化方法和资源free方法
init初始化方法主要是进行ffmpeg所必需的编解码器的初始化和部分功能方法的参数配置,而free资源释放方法则是相应地进行必要的回收
Encoder模块的实现和细节分析
#include <stdio.h>
#include <string.h>
#include <math.h>
// 以下是必要的ffmpeg库
#include <libavutil/opt.h>
#include <libavcodec/avcodec.h>
#include <libavutil/channellayout.h>
#include <libavutil/common.h>
#include <libavutil/imgutils.h>
#include <libavutil/mathematics.h>
#include <libavutil/samplefmt.h>
// 移植Android平台后用到的LOG方法
#define LOGTAG "android-ffmpeg-tutorial01"
#define LOGI(...) androidlogprint(4, LOGTAG, VAARGS);
#define LOGE(...) androidlogprint(6, LOGTAG, VAARGS);
// 宏定义一些固定参数
#define DCODEFORMAT AVCODECIDH264??? // 编码格式,AVCODECIDH264为ffmpeg内定编码
#define PIXFMT AVPIXFMT_YUV420P????????????? // 帧像素格式
#define DWIDTH 640??????????????????????????????????????????? ? // 视频宽度,原始视频和编码后宽度不变
#define DHEIGHT 480??????????????????????????????????????????? // 视频高度,原始视频和编码后高度不变
#define BITRATE 400000
#define PACKETFRAMES 4??????????????????????????????????? // I帧和P帧 1:3,编码得到的packet中,一份I帧跟着三份P帧? IPPPIPPPIP.....
#define FRAMERATE 25???????????????????????????????????????? // 帧率,这里是25fps
// 定义一些全局变量
unsigned long long curpts = 0;??????????????????????????????????? // 记录帧数并为每一帧设置pts,pts保持递增,关于pts网上有几种说法但俺对此认识不多
int errorS = 0, ngot = 0; // more than 0 means error?? // errorS暂时无用, ngot用于获取“是否取得一个编码packet”的标记
AVCodec *codec;??????????????????????????????????????????????????????? // 编码器
AVCodecContext *cotx = NULL;????????????????????????????????? // 编码器上下文,主要进行参数设置,决定编码器的运行
AVFrame *pframe;????????????????????????????????????????????????????? // 帧,填入待编码数据然后交给编码方法处理
AVPacket pkt;????????????????????????????????????????????????????????????? // 包,原始帧编码得到packet,存放编码得到的数据
// 编码器环境初始化
static void initencoder() {
int i, j, k;
curpts = 0;
//avcodecinit();
avcodecregisterall();????????????????????????????????????????????????? // 登记所有编码解码器,调用ffmpegAPI的第一步
codec = avcodecfindencoder(DCODEFORMAT);???? // 查找并获取特定编码器,这里是H264编码器
if (!codec) { // 找不到目标编码器,退出
errorS = 1;
//LOGI("Codec not found\n");
printf("Codec not found\n");
exit(1);
}
cotx = avcodecalloccontext3(codec);??????????????????????? // 根据编码器分配上下文,把“上下文”理解为一种参数设置器
if (!cotx) { // 无法分配上下文,退出
errorS = 1;
//LOGI("Could not allocate video codec context\n");
printf("Could not allocate video codec context\n");
exit(1);
}
cotx->bitrate = BITRATE;?????????????????????????????????????????????????????????????????????? // set bit rate
cotx->width = DWIDTH;
cotx->height = DHEIGHT;
cotx->timebase = (AVRational){1, FRAMERATE};???????????????????????????????? // 这里timebase.num = 1, timebase.dem = 25
cotx->gopsize = PACKETFRAMES;?????????????????????????????????????????????????????? // 见宏定义那里的注释
cotx->maxbframes = 0;??????????????????????????????????????????????????????????????????????? // 最大B帧数目?!当前不想要B帧。。
cotx->pixfmt = PIXFMT;???????????????????????????????????????????????????????????????????????? // 帧像素格式
avoptset(cotx->priv_data, "preset", "slow", 0);???????????????????????????????????? // 待查。。声明头文件未知
if (avcodecopen2(cotx, codec, NULL) < 0) {????????????????????????????????????????? // 以“上下文”配置的参数开启编码器,失败退出
//LOGI("Could not open codec\n");
printf("Could not open codec\n");
exit(1);
}
pframe = avframealloc();????????????????????????????????????????????????????????????????????? // 分配帧
if (!pframe) {
//LOGI("Could not allocate video frame\n");
printf("Could not allocate video frame\n");
exit(1);
}
// set the format of frame
pframe->format = cotx->pixfmt;????????????????????????????????????????????????????????????? // 指定帧像素格式
pframe->width = cotx->width;?????????????????????????????????????????????????????????????????? // 指定宽高
pframe->height = cotx->height;
// the last para 32 is unknown
errorS = avimagealloc(pframe->data, pframe->linesize,????????????????????? // 根据宽高以及帧格式进行分配,为帧挂载图像资源buffer
cotx->width, cotx->height, cotx->pix_fmt, 32);????????????????????????????????????? // pframe->data[0,1,2,...] 为buff指针
if (errorS < 0) {???????????????????????????????????????????????????????????????????????????????????? // pframe->linesize[0,1,2,...] 本格式下一行(width)像素的byte数目
//LOGI("Could not allocate raw picture buffer\n");??????????????????????????????? // 以YV12的640x480一帧为例
printf("Could not allocate raw picture buffer\n");?????????????????????????????????? // pframe->data[0] 存放Y信息,pframe->linesize[0] 原始帧中表示一行像素
exit(1);????????????????????????????????????????????????????????????????????????????????????????????????? // 的Y所需的byte数目,这里是640
}??????????????????????????????????????????????????????????????????????????????????????????????????????????? // pframe->data[1]和pframe->data[2]分别为VU信息,而pframe->linesize[1]
return;?????????????????????????????????????????????????????????????????????????????????????????????????? // pframe->linesize[2]则都为320
}
// 释放资源
static void freeencoder() {
avfreepacket(&pkt);????????????????????????????????????????????????????????????????????????????? // 释放packet的挂载buff,frame和packet可以说分别对应“原始”和“编码”资源
avcodecclose(cotx);????????????????????????????????????????????????????????????????????????????? // 关闭“上下文”和编码器
avfree(cotx);???????????????????????????????????????????????????????????????????????????????????????? // 释放“上下文”资源
avfreep(&pframe->data[0]);????????????????????????????????????????????????????????????????? // 释放frame图像帧buff
avframefree(&pframe);???????????????????????????????????????????????????????????????????????? // 释放图像帧
}
/*
* for yv12 frame -> h264 only
* be sure the encoder has been initialized
*/
static int packetframeencoder(const char *rawframes, int bytesize) {
avfreepacket(&pkt);??????????????????????????????????????????????????????????????????????????????? // 释放packet的buff,即pkt.data,这里pkt为结构体,pkt.size为大小
avinitpacket(&pkt);????????????????????????????????????????????????????????????????????????????????? // 初始化packet
pkt.data = NULL;??????????????????????????????????????????????????????????????????????????????????? // packet编码时用于装载编码后数据,因此data设为NULL,长度为0
pkt.size = 0;
if (rawframes == NULL) {
errorS = avcodecencodevideo2(cotx, &pkt, NULL, &ngot);
} else {
// Y????????????????????????????????????????????????????????????????????????????????????????????????????????? // 按照原始资源格式,将待编码数据装填帧,这里原始格式为YV12
memcpy(pframe->data[0], rawframes, (int)(cotx->height * cotx->width));
// Cr, Cb
memcpy(pframe->data[1], rawframes + (int)(cotx->height * cotx->width), (int)(cotx->height * cotx->width / 4));
memcpy(pframe->data[2], rawframes + (int)(cotx->height * cotx->width * 1.25), (int)(cotx->height * cotx->width / 4));
pframe->pts = ++curpts;????????????????????????????????????????????????????????????????????????? // 为每一帧设置pts,关于时间戳的用途机制不明,但是应当为单增序
errorS = avcodecencodevideo2(cotx, &pkt, pframe, &ngot);?????????????????? // 以指定的编码器和原始帧进行编码,编码帧打包入pkt,ngot带回
}?????????????????????????????????????????????????????????????????????????????????????????????????????????????? // 是否生成包的信息,有时一帧拆成多包,有时多帧打一包,因此需要
if (errorS < 0) {???????????????????????????????? // 判断编码过程是否报错???????????????? // 确定本次输入一帧原始后是否有packet生成,
//LOGI("Error encoding frame\n");????????????????????????????????????????????????????????? // 有时连续输入多帧才能编码打包,毕竟像h264这种编码,需要针对
printf("Error encoding frame\n");??????????????????????????????????????????????????????????? // 多帧之间的关系进行编码压缩,因此需要先输入多个帧才行
exit(1);??????????????????????????????????????????????????????????????????????????????????????????????????? // (待考证!)之前的 cotx->gop_size 就是决定IPPP,一I帧三P帧
}
if (!ngot) {??????????????????????????????????????????????????????????????????????????????????????????????? // 判断是否有packet生成,若有则 ngot != 0
//avfreepacket(&pkt);
return 1;
}
//printf("One Packet\n");
return 0;
}
void encodeTest(FILE *fin, FILE *fout) {????????????????????????????????????????????? ? // 编码测试,读取YV12格式数据,然后H264编码,最后写入文件
int buffsize = DHEIGHT * DWIDTH * 1.5;?????????????????????????????????????????????? // 开一个buff正好一次装下一帧,YV12,640x480
char buff[buffsize];
int cot = 0;
while (fread(buff, 1, buffsize, fin) > 0) {???????????????????????????????????????????????? // 读取一帧
//printf("One Frame: %lld\n", curpts);
if (packetframeencoder(buff, buffsize)) {????????????????????????????????????????????? // 判断是否有packet生成
continue;
}
cot += fwrite(pkt.data, 1, pkt.size, fout);?????????????????????????????????????????????? // 若有,则写入文件
// printf("--->%d\n", cot);
}
// for delayed frames?????????????????????????????????????????????????????????????????????????? // 前面说过,有时需要输入多帧才有packet,但是文件已读完时。。。
while (!packetframeencoder(NULL, 0)) {???????????????????????????????????????????? // 以NULL特殊参数告知编码器“没有更多输入帧啦,请直接编码输出packet“
cot += fwrite(pkt.data, 1, pkt.size, fout);????????????????????????????????????????????? // 连续检测,直到不再有packet产生,说明编码完毕
// printf("--->%d\n", cot);
}
}
int main() {
char fileA[20] = "target.yuv\0";
char fileB[20] = "encode.264\0";
FILE *fin = fopen(fileA, "rb");
if (!fin) {
printf("Input File %s not found\n", fileA);
return;
}
FILE *fout = fopen(fileB, "wb");
initencoder();
printf("Encoder Initialized\n");
encodeTest(fin, fout);
printf("Encoding Completed\n");
freeencoder();
printf("Encoder Free\n");
fclose(fin);
fclose(fout);
return 0;
}
Decoder模块的实现和细节分析 ·「和Encoder模块基本一致的流程,只是部分关键流程与编码呈逆向关系」
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <libavutil/opt.h>
#include <libavcodec/avcodec.h>
#include <libavutil/channel_layout.h>
#include <libavutil/common.h>
#include <libavutil/imgutils.h>
#include <libavutil/mathematics.h>
#include <libavutil/samplefmt.h>
#define LOG_TAG "android-ffmpeg-tutorial01"
#define LOGI(...) __android_log_print(4, LOG_TAG, __VA_ARGS__);
#define LOGE(...) __android_log_print(6, LOG_TAG, __VA_ARGS__);
#define DCODEFORMAT AV_CODEC_ID_H264
#define PIXFMT AV_PIX_FMT_YUV420P
#define DWIDTH 640
#define DHEIGHT 480
#define BITRATE 400000
#define PACKETFRAMES 4
#define FRAMERATE 25
int errorS = 0, ngot = 0; // more than 0 means error
AVCodec *codec;
AVCodecContext *cotx = NULL;
AVFrame *pframe;
AVPacket pkt;
char *decodeFrame = NULL, *df = NULL;??????????????????????????????????????????????????????? // 用来存放解码帧
static void init_decoder() {??????????????????????????????????????????????????????????????????????????????? // 初始化解码模块,可以注意到初始化流程和Encoder大同小异
??? int i, j, k;
??? //avcodec_init();
??? avcodec_register_all();
??? codec = avcodec_find_decoder(DCODEFORMAT);???????????????????????????????????? // 查询解码器
??? if (!codec) {
??? ??? errorS = 1;
??? ??? //LOGI("Codec not found\n");
??? ??? exit(1);
??? }
??? cotx = avcodec_alloc_context3(codec);???????????????????????????????????????????????????????? // 获取解码器“上下文”
??? if (!cotx) {
??? ??? errorS = 1;
??? ??? //LOGI("Could not allocate video codec context\n");
??? ??? exit(1);
??? }
??? cotx->bit_rate = BITRATE; // set bit rate???????????????????????????????????????????????????????? // 在FFMPEG的解码Demo中,”上下文“并未进行这类设置
??? cotx->width = DWIDTH;????????????????????????????????????????????????????????????????????????????????? // 有查到说解码时根据读取到的编码packet来获取”上下文“
??? cotx->height = DHEIGHT;?????????????????????????????????????????????????????????????????????????????? // 信息,比如宽高就是解码packet后获取的,不需要设置
??? cotx->time_base = (AVRational){1, FRAMERATE};
??? cotx->gop_size = PACKETFRAMES;
??? cotx->max_b_frames = 0; // what
??? //cotx->frame_number = 1
??? cotx->pix_fmt = PIXFMT;
??? if(codec->capabilities&CODEC_CAP_TRUNCATED)
??????? cotx->flags|= CODEC_FLAG_TRUNCATED; /* we do not send complete frames */
??? if (avcodec_open2(cotx, codec, NULL) < 0) {
??? ??? //LOGI("Could not open codec\n");
??? ??? exit(1);
??? }
??? pframe = av_frame_alloc();
??? if (!pframe) {
??? ??? //LOGI("Could not allocate video frame\n");
??? ??? exit(1);
??? }
??? av_init_packet(&pkt);
??? decodeFrame = (char*)malloc((int)(DHEIGHT * DWIDTH * 1.5));
??? return;
}
static void free_decoder() {
??? avcodec_close(cotx);
??? av_free(cotx);
??? av_frame_free(&pframe);
??? free(decodeFrame);
}
/*
?* for h264 -> yv12 frame only
?* be sure the decoder has been initialized
?*/
static int packet_frame_decoder(char *enpacket, int bytesize, int *haslen) {
??? // set raw data
??? pkt.data = enpacket;
??? pkt.size = bytesize;
??? // printf("(o..o): %d\n", bytesize);
??? (*haslen) = avcodec_decode_video2(cotx, pframe, &ngot, &pkt);
??? // printf("(*..o)\n");
??? if ((*haslen) < 0) {
??? ??? //LOGI("Error encoding frame\n");
??? ??? errorS = 1;
??? ??? exit(1);
??? }
??? // printf("(!..o): %d\n", *haslen);
??? if (!ngot || cotx->width != DWIDTH || cotx->height != DHEIGHT || cotx->pix_fmt != PIXFMT) { // failed to decode a frame
??? ??? //errorS = 1;??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? // sth wrong with format
??? ??? return 1;
??? }
??? // process decoded data in pframe
??? df = decodeFrame;
??? // Y
??? memcpy(df, pframe->data[0], pframe->linesize[0] * cotx->height);
??? df += pframe->linesize[0] * cotx->height;
??? // Cr, Cb
??? memcpy(df, pframe->data[1], (int)(pframe->linesize[1] * cotx->height / 2));
??? df += (int)(pframe->linesize[1] * cotx->height / 2);
??? memcpy(df, pframe->data[2], (int)(pframe->linesize[2] * cotx->height / 2));
??? // printf("=====>>%d\n", pframe->linesize[0] * cotx->height + (int)(pframe->linesize[1] * cotx->height / 2) + (int)(pframe->linesize[2] * cotx->height / 2));
??? return 0;
}
// for each packet, find its header of I Frame
int findIFrame(unsigned char *buff, int buffsize) {
??? int ps, len = buffsize-3;
??? for (ps = 0; ps < len; ++ps) {
??? ??? if (buff[ps] == 0x0 && buff[ps+1] == 0x0
??? ??? ?&& buff[ps+2] == 0x0 && buff[ps+3] == 0x1
??? ??? ? && buff[ps+4] == 0x67) {
??? ??? ??? return ps;
??? ??? }
??? }
??? return -1;
}
// for each packet, find its header of P Frame
int findPFrame(unsigned char *buff, int buffsize) {
??? int ps, len = buffsize-3;
??? for (ps = 0; ps < len; ++ps) {
??? ??? if (buff[ps] == 0x0 && buff[ps+1] == 0x0
??? ??? ?&& buff[ps+2] == 0x0 && buff[ps+3] == 0x1
??? ??? ? && buff[ps+4] == 0x41) {
??? ??? ??? return ps;
??? ??? }
??? }
??? return -1;
}
// for each packet, find its header of I or P Frame
int findIPFrame(unsigned char *buff, int buffsize) {
??? int ps, len = buffsize-3;
??? for (ps = 0; ps < len; ++ps) {
??? ??? if (buff[ps] == 0x0 && buff[ps+1] == 0x0
??? ??? ?&& buff[ps+2] == 0x0 && buff[ps+3] == 0x1) {
??? ??? ???? if (buff[ps+4] == 0x67 || buff[ps+4] == 0x41) {
??? ??? ??? ??? return ps;
??? ??? ??? }
??? ??? }
??? }
??? return -1;
}
// make sure the I Frame at the top of file
void decodeTest(FILE *fin, FILE *fout) {
??? int BUFFLEN = 30720, DEL = 3, COT = 0, tmmp;
??? char buff[BUFFLEN];
??? int buffsize = 0, packetsize = 0, haslen;
??? buffsize = fread(buff, 1, BUFFLEN, fin);
??? if (buffsize <= 0) {
??? ??? printf("Empty File\n");
??? ??? return;
??? }
??? packetsize = findIFrame(buff, buffsize);
??? if (packetsize < 0) {
??? ??? printf("can‘t find I frame, broken\n");
??? ??? return;
??? }
??? buffsize -= packetsize;
??? memmove(buff, buff+packetsize, buffsize);
??? buffsize += fread(buff+buffsize, 1, BUFFLEN - buffsize, fin);
??? packetsize = findIPFrame(buff+DEL, buffsize-DEL) + DEL;
??? while (packetsize > DEL) { // break if can‘t find next frame
??? ??? // COT += packetsize;
??? ??? // printf("%d---%d--->%d\n", buffsize, packetsize, COT);
??? ??? // int t = 0;
??? ??? // for (t = 0; t < 10; ++t) {
??? ??? // ??? printf("%x-",buff[t]);
??? ??? // }
??? ??? // printf("\n");
??? ??? if (!packet_frame_decoder(buff, packetsize, &haslen)) {
??? ??? ??? fwrite(decodeFrame, 1, (int)(DHEIGHT * DWIDTH * 1.5), fout);
??? ??? }
??? ??? buffsize -= packetsize;
??? ??? memmove(buff, buff+packetsize, buffsize);
??? ??? buffsize += fread(buff+buffsize, 1, BUFFLEN - buffsize, fin);
??? ??? packetsize = findIPFrame(buff+DEL, buffsize-DEL) + DEL;
??? }
??? // for the last frame
??? if (!packet_frame_decoder(buff, buffsize, &haslen)) {
??? ??? fwrite(decodeFrame, 1, (int)(DHEIGHT * DWIDTH * 1.5), fout);
??? }
??? // for delayed frame
??? while (!packet_frame_decoder(NULL, 0, &haslen)) {
??? ??? fwrite(decodeFrame, 1, (int)(DHEIGHT * DWIDTH * 1.5), fout);
??? }
}
int main() {
??? char fileA[20] = "encode.264\0";
??? char fileB[20] = "decode.yuv\0";
??? FILE *fin = fopen(fileA, "rb");
??? if (!fin) {
??? ??? printf("Input File %s not found\n", fileA);
??? ??? return;
??? }
??? FILE *fout = fopen(fileB, "wb");
??? init_decoder();
??? decodeTest(fin, fout);
??? free_decoder();
??? fclose(fin);
??? fclose(fout);
??? return 0;
}
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。