標籤:
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;
}
Linux下FFMPEG--H264--編碼&&解碼的C實現與相關原理詳解