简介
这里实现了一个简单的纯视频播放器,其数据流图如下
图中已经表述的很清楚了,解复用、解码、分辨率调节都通过ffmpeg完成,而显示则由SDL完成。其中解复用、解码和分辨率调整部分可以参看ffmpeg给出的示例文件(解复用和解码可参考demuxing_decoding.c
,分辨率调整可参考scaling_video.c
)。
分辨率调节并不是必须的,但如果输入视频的分辨率是1080P的话,则最终的显示需要创建一个1920*1080大小的窗口,否则视频将显示不完全。
头文件
解复用需要用到libavformat
库,解码需要用到libavcodec
库,而分辨率调整需要用到libswscale
和libavutil
库,显示需要用到SDL
库,关于ffmpeg+SDL工程的配置可以参考之前写过的一篇文章 。
由于这里创建了main.cpp
源文件,而ffmpeg是用C写的,因此在包含头文件时要做些处理
#include <iostream>
#ifdef __cplusplus
extern "C" {
#endif
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#ifdef __cplusplus
}
#endif
#include <SDL.h>
using namespace std;
源码
#include <iostream>
#ifdef __cplusplus
extern "C" {
#endif
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#ifdef __cplusplus
}
#endif
#include <SDL.h>
using namespace std;
int InitCodec(AVFormatContext* fmt_ctx, AVMediaType type, AVCodecContext*& video_dec_ctx, AVStream*& videoStream, int& streamIdx)
{
int ret = 0;
const AVCodec* dec = NULL;
//-- 查找指定媒体流信息
streamIdx = av_find_best_stream(fmt_ctx, type, -1, -1, NULL, 0);
if (streamIdx < 0)
{
cout << "find stream error!" << endl;
return -1;
}
videoStream = fmt_ctx->streams[streamIdx];
//-- 获取解码器
dec = avcodec_find_decoder(videoStream->codecpar->codec_id);
if (dec == NULL)
{
cout << "find decoder error!" << endl;
return -1;
}
//-- 创建编码器
video_dec_ctx = avcodec_alloc_context3(dec);
if (video_dec_ctx == NULL)
{
cout << "create decoder error!" << endl;
return -1;
}
//-- 配置编码器
ret = avcodec_parameters_to_context(video_dec_ctx, videoStream->codecpar);
if (ret < 0)
{
cout << "config decoder failure!" << endl;
return -1;
}
//--- 打开编码器
ret = avcodec_open2(video_dec_ctx, dec, NULL);
if (ret != 0)
{
cout << "open decoder error!" << endl;
return -1;
}
return 1;
}
int main(int argc, char** argv)
{
const char* fileName;
int ret = 0;
//-- 解复用解码
AVFormatContext* fmt_ctx = NULL;
AVCodecContext* video_dec_ctx = NULL;
AVFrame* frame = NULL;
AVPacket* pkt = NULL;
AVStream* videoStream = NULL;
int videoStreamIdx = -1;
//-- 分辨率调整
SwsContext* sws_ctx = NULL;
uint8_t* pYUVFrame_data[4];
int pYUVFrame_linesize[4];
int src_w, src_h;
int dst_w, dst_h;
//-- SDL相关
SDL_Window* screen;
SDL_Renderer* sdlRenderer;
SDL_Texture* sdlTexture;
SDL_Rect sdlRect;
if (argc != 2)
{
cout << "The number if input parameter error" << endl;
return 1;
}
fileName = argv[1];
//-- 打开文件,并初始化fmt_ctx
if ((ret = avformat_open_input(&fmt_ctx, fileName, 0, 0)) < 0)
{
cout << "Open file error!" << endl;
return 1;
}
//-- 读取流信息
if ((ret = avformat_find_stream_info(fmt_ctx, 0)) < 0)
{
cout << "Retrieve stream info error!" << endl;
return 1;
}
//-- 打印流信息
av_dump_format(fmt_ctx, 0, fileName, 0);
//-- 初始化解码器
ret = InitCodec(fmt_ctx, AVMEDIA_TYPE_VIDEO, video_dec_ctx, videoStream, videoStreamIdx);
if(ret <0)
{
cout << "init codec error!" << endl;
return -1;
}
//-- 获取视频长宽信息
src_w = videoStream->codecpar->width;
src_h = videoStream->codecpar->height;
//-- 设置目标长宽信息
dst_w = src_w / 2;
dst_h = src_h / 2;
//-- 创建缩放对象(注:像素格式根据视频的具体信息而定,不一定都是AV_PIX_FMT_YUV420P)
sws_ctx = sws_getContext(
src_w, src_h, AV_PIX_FMT_YUV420P,
dst_w, dst_h, AV_PIX_FMT_YUV420P,
SWS_BICUBIC, NULL, NULL, NULL);
//-- 分配缩放后数据的存储空间
av_image_alloc(pYUVFrame_data, pYUVFrame_linesize,
dst_w, dst_h, AV_PIX_FMT_YUV420P, 16);
//-- 初始化SDL
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER);
//-- 创建SDL窗口
screen = SDL_CreateWindow("MyWindow", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
dst_w, dst_h, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
//-- 创建SDL渲染
sdlRenderer = SDL_CreateRenderer(screen, 0, -1);
//-- 创建SDL纹理
sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING, dst_w, dst_h);
//-- 初始化显示区域
sdlRect = { 0, 0, dst_w, dst_h };
//-- 分配包空间和帧空间
pkt = av_packet_alloc();
frame = av_frame_alloc();
//-- 主循环
while (av_read_frame(fmt_ctx, pkt) >= 0)
{
//-- 筛选视频流包
if (pkt->stream_index == videoStreamIdx)
{
//-- 将视频包发送到解码器
ret = avcodec_send_packet(video_dec_ctx, pkt);
if (ret < 0)
{
cout << "Send pkt error!" << endl;
break;
}
while (ret >= 0)
{
//-- 读取解码帧
ret = avcodec_receive_frame(video_dec_ctx, frame);
if (ret < 0)
{
if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN))
{
break;
}
cout << "Receive frame error!" << endl;
break;
}
//-- 分辨率调整
sws_scale(sws_ctx,
(const unsigned char* const*)frame->data, frame->linesize,
0, src_h,
pYUVFrame_data, pYUVFrame_linesize);
//-- 更新到SDL纹理
SDL_UpdateYUVTexture(sdlTexture, &sdlRect,
pYUVFrame_data[0], pYUVFrame_linesize[0],
pYUVFrame_data[1], pYUVFrame_linesize[1],
pYUVFrame_data[2], pYUVFrame_linesize[2]);
//-- 清除源有渲染
SDL_RenderClear(sdlRenderer);
//-- 更新渲染
SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, &sdlRect);
//-- 显示渲染
SDL_RenderPresent(sdlRenderer);
//-- 延时40ms
SDL_Delay(40);
}
}
//-- 复位内存空间数据
av_frame_unref(frame);
av_packet_unref(pkt);
}
av_frame_unref(frame);
av_packet_unref(pkt);
//-- 释放资源
SDL_DestroyTexture(sdlTexture);
SDL_DestroyRenderer(sdlRenderer);
SDL_DestroyWindow(screen);
av_freep(&pYUVFrame_data[0]);
sws_freeContext(sws_ctx);
avcodec_free_context(&video_dec_ctx);
avformat_close_input(&fmt_ctx);
av_frame_free(&frame);
av_packet_free(&pkt);
return 0;
}