最學習音視頻,先了解了音視頻的一些理論知識知識,比如從視頻錄採集,編碼、封裝、推流、拉流解封裝、解碼、同步、播放等一系列的過程。瞭解了視頻的編碼原理i,p,b幀的處理等,瞭解了h264,yuv等原理。接着學習ffmpeg的知識,ffmpeg就是對上面這些步驟的集大成者。首先從ffmpeg的一些常用的結構體瞭解起。
小插曲:如果視頻不編碼那他的體積計算公式:1小時3600秒,1秒25張圖(一幀),一第圖1920*1080個像素,RGB24格式一個像素佔3個字節。
下面是音頻的體積計算:4分鐘*1分鐘60s*1s採點44.1k次,一個點佔兩個字節,如果是雙聲道還得乘以2
知道了上面的ffmpeg的常用結構體以後,就做了如下一個小例子,是在VisualStudio下寫的,動態鏈接庫的配置花了我不少時間,不裏就不介紹了。別的文章有介紹。下面直接來看代碼 :
#include <stdio.h>
#include "stdafx.h"
#define __STDC_CONSTANT_MACROS
#ifdef _WIN32
//Windows
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
#include "SDL2/SDL.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <SDL2/SDL.h>
#ifdef __cplusplus
};
#endif
#endif
int main(int argc, char* argv[])
{
AVFormatContext *pFormatCtx;
int i, videoindex;
AVCodecContext *pCodecCtx;
AVCodec *pCodec;
AVFrame *pFrame,*pFrameYUV;
unsigned char *out_buffer; //存儲一幀像素數據緩衝區
AVPacket *packet;
int ret, got_picture;
int screen_w,screen_h;
//去掉黑邊的轉換
struct SwsContext *img_convert_ctx;
//char filepath[]="bigbuckbunny_480x272.h265";
//char filepath[]="Titanic.ts";
char filepath [] = "hello_talk.mp4";
av_register_all();
avformat_network_init();
pFormatCtx = avformat_alloc_context();
//打開文件
if(avformat_open_input(&pFormatCtx,filepath,NULL,NULL)!=0){
printf("Couldn't open input stream.\n");
return -1;
}
//獲取流
if(avformat_find_stream_info(pFormatCtx,NULL)<0){
printf("Couldn't find stream information.\n");
return -1;
}
videoindex=-1;
for(i=0; i<pFormatCtx->nb_streams; i++)
if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){
videoindex=i;
break;
}
if(videoindex==-1){
printf("Didn't find a video stream.\n");
return -1;
}
//獲得視頻流
pCodecCtx=pFormatCtx->streams[videoindex]->codec;
//找到解碼器
pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
if(pCodec==NULL){
printf("Codec not found.\n");
return -1;
}
//打開解碼器
if(avcodec_open2(pCodecCtx, pCodec,NULL)<0){
printf("Could not open codec.\n");
return -1;
}
//輸出視頻信息到控制檯
printf("時長:%d\n",pFormatCtx->duration);
printf("格式:%s\n",pFormatCtx->iformat->name);
printf("寬高:%d*%d\n",pFormatCtx->streams[videoindex]->codec->width,pFormatCtx->streams[videoindex]->codec->height);
//輸出視頻信息到文件中
FILE *fp = fopen("info.txt","wb+");
fprintf(fp,"時長:%d\n",pFormatCtx->duration);
fprintf(fp,"格式:%s\n",pFormatCtx->iformat->name);
fprintf(fp,"寬高:%d*%d\n",pFormatCtx->streams[videoindex]->codec->width,pFormatCtx->streams[videoindex]->codec->height);
fclose(fp);
//Output Info-----------------------------
printf("---------------- File Information ---------------\n");
av_dump_format(pFormatCtx,0,filepath,0);
printf("-------------------------------------------------\n");
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
screen_w = pCodecCtx->width;
screen_h = pCodecCtx->height;
int frame_cnt = 0;
//原始幀
pFrame=av_frame_alloc();
//去掉黑邊的幀
pFrameYUV=av_frame_alloc();
//重要一步,容易忽略的
out_buffer=(unsigned char *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height,1));
//存儲一幀像素數據緩衝區
av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize,out_buffer,AV_PIX_FMT_YUV420P,pCodecCtx->width, pCodecCtx->height,1);
//將packet數據寫到h264文件中
FILE* fp264 = fopen("hello_talk.h264","wb+");
FILE* fpyuv = fopen("hello_talk.yuv","wb+");
screen_w = pCodecCtx->width;
screen_h = pCodecCtx->height;
packet=(AVPacket *)av_malloc(sizeof(AVPacket));
while(av_read_frame(pFormatCtx,packet)>=0){
if(packet->stream_index == videoindex){
fwrite(packet->data,1,packet->size,fp264);
//將一個h264的packet解壓成一個yuv幀
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
if(ret < 0){
printf("Decode Error.\n");
return -1;
}
if(got_picture){
//去掉因爲cpu的原因導致的兩端的黑邊,得到的數據存放在pFrameYUV中
sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
fwrite(pFrameYUV->data[0],1,screen_w*screen_h,fpyuv); //存Y的數據
fwrite(pFrameYUV->data[1],1,screen_w*screen_h/4,fpyuv);//存U的數據 寬高是Y的一半
fwrite(pFrameYUV->data[2],1,screen_w*screen_h/4,fpyuv);//存V的數據 寬高是Y的一半
}
av_free_packet(packet);
}
}
fclose(fp264);
fclose(fpyuv);
system("pause");
return 0;
}
首先 準備一個視頻資源,運行這個程序可以實現以下效果:
1. 輸出視頻的時長、格式、寬高 打印到控制檯
2.輸出視頻的時長、格式、寬高到info.txt文件中
3.生成視頻的h264文件
4.生成視頻的yuv文件
我的目錄如下:
上面我分別測試了兩個封裝格式的視頻:一個hello_talk.mp4,一個Tianic.ts,對它做了解封裝操作獲取視頻碼流AvPacket,保存生成了h264文件,接着對視頻碼流進行解碼生成幀AvFrame,保存生成了yuv文件。另外我還在網上下了一個yuv的播放器,試驗發現設置好size後yuv格式後可以正常播放。
至此一個小例子嘗試完成。寫yuv文件的時候還可以選擇只寫Y(UV點的個數是Y的1/4),這樣就只有黑白的視頻了,感覺很神奇。自己寫的一些學的理論知識得到了印證,學習的動力就更足了!
這是我寫ffmpeg的第一個例子,後來還是繼續記錄我學習ffmpeg的點滴成果。