最学习音视频,先了解了音视频的一些理论知识知识,比如从视频录采集,编码、封装、推流、拉流解封装、解码、同步、播放等一系列的过程。了解了视频的编码原理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的点滴成果。