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的点滴成果。

 

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