ffmpeg學習的第一個小有成就的例子

最學習音視頻,先了解了音視頻的一些理論知識知識,比如從視頻錄採集,編碼、封裝、推流、拉流解封裝、解碼、同步、播放等一系列的過程。瞭解了視頻的編碼原理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的點滴成果。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章