FFmpeg普及率高,相对易于使用,资料也多,甚至还有视频学习课程,FFmpeg属于GPL开源的,有助于学习,如果商业应用,切记GPL条款,几个著名的视频播放软件因编译改变了FFmpeg源代码而没有开源,作者光荣的登上了耻辱名单。
在学习FFmpeg过程中,遇到了点困难,因为直接下载使用了最新4.1版的,很多函数被否决,网上的资料行不通,自带的例程难以理解,所以又找到最低3.2版的,还是行不通,还好部分函数被否决,网上的资料大部分是2.x版的,所以试着找3.x版的和2.x不同的部分,结果成功了。需要说明的是2.x版的方便易用,好理解,3.x版变得臃肿不易理解。既然3.x版的成功了,为何不4.1版的,所以将4.1版的替换进去,结果成功了,4.1版相对3.2版改变不多,av_register_all()被否决了,需要替换。使用4.1版的意义在于当前大部分视频都可以轻松搞定。
1. Visual C++ 2017 创建控制台程序。
2. 到http://ffmpeg.org/下载Windods文件,通过以下选项下载:
Version:4.1
Architecture:Windods32-bit
Linking:下载Shared和Dev两个压缩文件,Shared需要DLL,Dev需要库和头文件。分别解压,将ffmpeg-4.1-win32-shared->bin目录内的.dll考入解决方案的Debug目录中。在解决方案目录中新建FFmpeg目 录,拷入ffmpeg-4.1-win32-dev内的include和lib两个目录。
3. 创建开发环境:
3.1 包含编译文件
3.1.1 包含头目录:项目->属性->VC++目录->包含目录:F:\FFmpegTest\FFmpeg\include。
3.1.2 包含库目录:项目->属性->VC++目录->库目录:F:\FFmpegTest\FFmpeg\lib。
3.1.3 包含链接库文件:项目->属性->链接器->输入->附加依赖项:avcodec.lib;avdevice.lib;avfilter.lib;avformat.lib;avutil.lib;postproc.lib;swresample.lib;swscale.lib。
4. 输入代码:
#include <iostream>
#include <stdio.h>
#define __STDC_CONSTANT_MACROS
extern "C"
{
#include "libavutil/imgutils.h" //av_image使用
#include "libavcodec/avcodec.h" //编解码处理
#include "libavformat/avformat.h" //文件封装处理
#include "libswscale/swscale.h"//图像变换
#include "libavdevice/avdevice.h"
};
void PrintErrStr(const char *str)
{
printf(str);
printf("\n\n");
system("pause");
}
int Decode(AVCodecContext *dec_ctx, AVFrame *frame, AVPacket *pkt)//解码
{
int ret = avcodec_send_packet(dec_ctx, pkt);// 先发送包数据到解码上下文中
if (ret < 0)
{
PrintErrStr("发送数据包进行解码时出错.");
return ret;
}
return avcodec_receive_frame(dec_ctx, frame);// 然后从解码上下文中读取帧数据到frame对象中
}
int main()
{
AVFormatContext *pFormatCtx = NULL;//主要用于处理封装格式(FLV/MKV/RMVB等)
AVCodecContext *pVideoCodecCtx = NULL;
AVCodecContext *pAudioCodecCtx = NULL;
AVCodec *pVideoCodec = NULL;
AVCodec *pAudioCodec = NULL;
AVFrame *pFrame; //存储非压缩的数据(视频对应RGB/YUV像素数据,音频对应PCM采样数据)
AVPacket packet; //存储压缩数据(视频对应H.264等码流数据,音频对应AAC/MP3等码流数据)
int iHour, iMinute, iSecond, iTotalSeconds;//HH:MM:SS
int videoStreamIndex, audioStreamIndex; //音视频流索引
int ret, FrameBytes;
const char In_FileName[] = "F:\\TestMov\\Titanic.ts";//输入文件
const char Out_h264_FileName[] = "F:\\TestMov\\output.h264";//输出h264文件
const char Out_rgb24_FileName[] = "F:\\TestMov\\output.rgb24";//输出h264文件
FILE *fp_Out_h264_File = fopen(Out_h264_FileName, "wb+");
FILE *fp_Out_rgb24_File = fopen(Out_rgb24_FileName, "wb+");
int OutImgW = 640;
int OutImgH = 272;
int OutImg_linesize[3] = {OutImgW * 3,0,0};
avdevice_register_all();//注册所有组件
if (avformat_open_input(&pFormatCtx, In_FileName, NULL, NULL) != 0)//打开输入视频文件
{
PrintErrStr("打开输入文件失败。");
return -1;
}
if (avformat_find_stream_info(pFormatCtx, NULL) < 0)//获取文件信息
{
PrintErrStr("没有发现流信息。");
return -1;
}
videoStreamIndex = -1;
audioStreamIndex = -1;
for (unsigned int i = 0; i < pFormatCtx->nb_streams; i++)//寻找流索引
{
if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)//查找视频流索引
{
videoStreamIndex = i;
}
else
if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)//查找音频流索引
{
audioStreamIndex = i;
}
}
if ((videoStreamIndex == -1)||(audioStreamIndex == -1))
{
PrintErrStr("没有发现视频或音频流。");
return -1;
}
pVideoCodec = avcodec_find_decoder(pFormatCtx->streams[videoStreamIndex]->codecpar->codec_id);//查找视频解码器
if (pVideoCodec == NULL)
{
PrintErrStr("没有找到视频解码器。");
return -1;
}
pAudioCodec = avcodec_find_decoder(pFormatCtx->streams[audioStreamIndex]->codecpar->codec_id);//查找音频解码器
if (pVideoCodec == NULL)
{
PrintErrStr("没有找到音频解码器。");
return -1;
}
pVideoCodecCtx = avcodec_alloc_context3(pVideoCodec);//申请视频解码空间
if (pVideoCodecCtx == NULL)
{
PrintErrStr("无法分配视频解码器。");
return -1;
}
pAudioCodecCtx = avcodec_alloc_context3(pAudioCodec);//申请音频解码空间
if (pAudioCodecCtx == NULL)
{
PrintErrStr("无法分配音频解码器。");
return -1;
}
avcodec_parameters_to_context(pVideoCodecCtx, pFormatCtx->streams[videoStreamIndex]->codecpar);
avcodec_parameters_to_context(pAudioCodecCtx, pFormatCtx->streams[audioStreamIndex]->codecpar);
if (avcodec_open2(pVideoCodecCtx, pVideoCodec, NULL) < 0) //打开视频解码器
{
PrintErrStr("没有打开视频解码器。");
return -1;
}
if (avcodec_open2(pAudioCodecCtx, pAudioCodec, NULL) < 0) //打开音频解码器
{
PrintErrStr("没有打开音频解码器。");
return -1;
}
pFrame = av_frame_alloc();//申请解压帧缓存
if (pFrame == NULL)
{
PrintErrStr("申请解压帧缓存失败。");
return -1;
}
FrameBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB24, pVideoCodecCtx->width, pVideoCodecCtx->height, 1);//获取视频帧字节数
uint8_t *pFrameBuffer = (uint8_t *)av_malloc(FrameBytes);//创建动态视频帧数组
while (av_read_frame(pFormatCtx, &packet) >= 0)//从文件中读取一个packet
{
if (packet.stream_index == videoStreamIndex)//如果是视频帧
{
fwrite(packet.data, packet.size, 1, fp_Out_h264_File);//写视频包文件
ret = Decode(pVideoCodecCtx, pFrame, &packet);//解码 packet->pFrame
if (ret == 0) //已得到解码的图像在pFrame里
{
struct SwsContext *pSwsCtx = NULL;
pSwsCtx = sws_getContext( pVideoCodecCtx->width, pVideoCodecCtx->height, pVideoCodecCtx->pix_fmt,//源宽度高度和格式YUV420
OutImgW, OutImgH, AV_PIX_FMT_RGB24,//转换到目标的宽度高度和格式
1,//缩放算法 SWS_FAST_BILINEAR
NULL, NULL, NULL);//初始化转换
if (!pSwsCtx)
{
PrintErrStr("无法初始化转换Frame帧。");
return -1;
}
sws_scale(pSwsCtx, pFrame->data, pFrame->linesize, 0, pVideoCodecCtx->height, &pFrameBuffer, OutImg_linesize);
fwrite(pFrameBuffer, FrameBytes, 1, fp_Out_rgb24_File);//写RGB视频文件
sws_freeContext(pSwsCtx);//释放转换
}
}
av_packet_unref(&packet);
}
//输出信息
puts("[文件信息]");
iTotalSeconds = (int)pFormatCtx->duration / 1000000; //S
iHour = iTotalSeconds / 3600;//小时
iMinute = iTotalSeconds % 3600 / 60;//分钟
iSecond = iTotalSeconds % 60;//秒
printf("播放时间:%02d:%02d:%02d\n", iHour, iMinute, iSecond);
printf("流 个 数:%d\n", pFormatCtx->nb_streams);
printf("封装格式:%s\n", pFormatCtx->iformat->long_name);
printf("\n");
puts("[视频信息]");
printf("编码格式:%s\n", pVideoCodec->long_name);
printf("视频码率:%I64d kb/s\n", pVideoCodecCtx->bit_rate / 1000);
printf("分 辨 率:%d * %d\n", pVideoCodecCtx->width, pVideoCodecCtx->height);
printf("\n");
puts("[音频信息]");
printf("编码格式:%s\n", pAudioCodec->long_name);
printf("音频码率:%I64d kb/s\n", pAudioCodecCtx->bit_rate / 1000);
printf("通 道 数:%d\n", pAudioCodecCtx->channels);
printf("采 样 率:%d\n", pAudioCodecCtx->sample_rate);
printf("\n");
fclose(fp_Out_h264_File);
fclose(fp_Out_rgb24_File);
av_free(pFrameBuffer);
av_frame_free(&pFrame);
avcodec_close(pVideoCodecCtx);
avcodec_close(pAudioCodecCtx);
avformat_close_input(&pFormatCtx);
printf("\n\n");
system("pause");
}
5. 编译运行:得到两个文件,一个是output.h264未解压的视频包文件,另一个是output.rgb24已解压的RGB24图像文件。
5.1 用ESEyE播放output.h264未解压的原始裸流视频文件,我下载的是Elecard StreamEye Tools绿色直接运行版,内有ESEyE播放软件,内还有pyuv用于播放yuv文件。
5.2 用RawPlayer播放output.rgb24,因属于裸流视频文件,打开文件时需要手动输入图像信息。
5.3 output.h264为h264压缩,属于原始裸流视频文件,因原始压缩包中含有播放用信息,所以可以播放。找了几个短视频文件均不是,所以用了雷霄骅博士的资源包,内有Titanic.ts为h264压缩,作为输入视频文件,分解出两个可播放的裸流视频文件。