最近又经常涉及到ffmpeg的使用,本来也用得不熟。现在新学开始整理一下
1. 总体步骤
打开一个媒体文件,获取帧数据统共两步:
- 创建一个AVFormatContext 去解封装,获取packet
- 通过formatcontext的解码器参数信息拿到解码器上下文AVCodecContext ,然后解码获取frame
2. 代码
进行封装,方便理解,封装参考[FFMPEG开发总结]1. 了解AVFormatContext(附带api封装跟转封装demo)
2.1 解封装类
#ifndef FFMPEGLEARN_MEDIACENTER_H
#define FFMPEGLEARN_MEDIACENTER_H
#include <string>
#include <vector>
#ifdef __cplusplus
extern "C" {
#endif
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#ifdef __cplusplus
}
#endif
class AvDemux{
public:
AvDemux();
~AvDemux();
public:
/****
作用:初始化解封装器
参数: name,需要初始化的文件名,可以是网络url
参数: ppDict,预设词典,封装结构提供,可以通过ffmpeg的解封装器找到需要的,一般不设置
****/
int initDeMux(std::string inputPath,AVDictionary** ppDict = NULL);
//获取流中的packet
int getStreamPacket(AVPacket* packet);
//所有的数据流
std::vector< AVStream* > m_pStreams[AVMEDIA_TYPE_NB];
private:
AVFormatContext* m_pDeMuxFmt;
};
#endif //FFMPEGLEARN_MEDIACENTER_H
#include "AvDemux.h"
AvDemux::AvDemux() {
m_pDeMuxFmt = nullptr;
}
AvDemux::~AvDemux() {
if(m_pDeMuxFmt != nullptr){
avformat_free_context(m_pDeMuxFmt);
m_pDeMuxFmt = nullptr;
}
}
int AvDemux::initDeMux(std::string inputPath, AVDictionary **ppDict) {
int iRet = 0;
//打开文件,并且初始化解封装器
iRet=avformat_open_input(&m_pDeMuxFmt, inputPath.c_str(), NULL, ppDict);
if (iRet)
return iRet;
//探测码流信息
iRet = avformat_find_stream_info(m_pDeMuxFmt, NULL);
if (iRet)
return iRet;
//组装码流
for (size_t i = 0; i < m_pDeMuxFmt->nb_streams; i++){
AVMediaType type = m_pDeMuxFmt->streams[i]->codecpar->codec_type;
if (type != AVMEDIA_TYPE_UNKNOWN)
m_pStreams[type].push_back(m_pDeMuxFmt->streams[i]);
}
if (iRet){
if (m_pDeMuxFmt){
avformat_close_input(&m_pDeMuxFmt);
}
}
return iRet;
}
int AvDemux::getStreamPacket(AVPacket *packet) {
int iRet = 0;
//覆写之前清堆内数据
av_packet_unref(packet);
//获取数据包
iRet=av_read_frame(m_pDeMuxFmt, packet);
return iRet;
}
2.2 解码类
#ifndef FFMPEGLEARN_AVDECODER_H
#define FFMPEGLEARN_AVDECODER_H
#ifdef __cplusplus
extern "C" {
#endif
#include "libavformat/avformat.h"
#ifdef __cplusplus
}
#endif
class AvDecoder {
public:
AvDecoder();
~AvDecoder();
int initDecoder(AVCodecParameters * codecParameters);
int sendPacket(AVPacket * pkt);
int receiveFrame(AVFrame * frame);
AVCodecContext * codec_ctx;
};
#endif //FFMPEGLEARN_AVDECODER_H
#include "AvDecoder.h"
#ifdef __cplusplus
extern "C" {
#endif
#include "libavcodec/avcodec.h"
#ifdef __cplusplus
}
#endif
AvDecoder::AvDecoder() {
codec_ctx = nullptr;
}
AvDecoder::~AvDecoder() {
if(codec_ctx != nullptr) {
avcodec_free_context(&codec_ctx);
codec_ctx = nullptr;
}
}
int AvDecoder::initDecoder(AVCodecParameters *codecParameters) {
const AVCodec * decoder = avcodec_find_decoder(codecParameters->codec_id);
codec_ctx = avcodec_alloc_context3(NULL);
avcodec_parameters_to_context(codec_ctx,codecParameters);
if(avcodec_open2(codec_ctx,decoder, nullptr)<0){
printf("视频解码器打开失败");
return -1;
}
return 0;
}
int AvDecoder::sendPacket(AVPacket *packet) {
return avcodec_send_packet(codec_ctx, packet);
}
int AvDecoder::receiveFrame(AVFrame * frame) {
return avcodec_receive_frame(codec_ctx,frame);
}
2.3 使用 with sdl2
代码结构参考[2] SDL的基础知识以及利用SDL播放视频
#include <iostream>
#ifdef __cplusplus
extern "C" {
#endif
#include <SDL2/SDL.h>
#ifdef __cplusplus
}
#include "AvDemux.h"
#include "AvDecoder.h"
#endif
using namespace std;
int WIDTH = 764, HEIGHT = 360; // SDL窗口的宽和高
int main() {
if (SDL_Init(SDL_INIT_EVERYTHING) < 0) { // 初始化SDL
cout << "SDL could not initialized with error: " << SDL_GetError() << endl;
}
SDL_Window *window = SDL_CreateWindow("Hello SDL world!", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WIDTH, HEIGHT, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); // 创建SDL窗口
if (NULL == window) {
cout << "SDL could not create window with error: " << SDL_GetError() << endl;
}
//新建一个渲染器
SDL_Renderer* sdlRenderer = SDL_CreateRenderer(window, -1, 0);
Uint32 pixformat = SDL_PIXELFORMAT_IYUV;
//新建纹理
SDL_Texture* sdlTexture = SDL_CreateTexture(sdlRenderer,pixformat, SDL_TEXTUREACCESS_STREAMING,WIDTH,HEIGHT);
//读取文件
std::string video_path = "/xxx/trump.mp4";
AvDemux demux;
AvDecoder decoder;
demux.initDeMux(video_path.c_str(), nullptr);
decoder.initDecoder(demux.m_pStreams[AVMEDIA_TYPE_VIDEO][0]->codecpar);
AVPacket * packet = av_packet_alloc();
AVFrame * frame = av_frame_alloc();
SDL_Event windowEvent; // SDL窗口事件
SDL_Rect sdlRect;
int ret = -1;
while(!demux.getStreamPacket(packet)) {
if (SDL_PollEvent(&windowEvent)) { // 对当前待处理事件进行轮询。
if (SDL_QUIT == windowEvent.type) { // 如果事件为推出SDL,结束循环。
cout << "SDL quit!!" << endl;
break;
}
}
if(decoder.sendPacket(packet) !=0 ){
std::cout << "send packet error" << std::endl;
continue;
}
while(!decoder.receiveFrame(frame)){
//更新纹理数据
// SDL_UpdateTexture( sdlTexture, NULL, frame->data, frame->width);
SDL_UpdateYUVTexture(sdlTexture, NULL,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]);
//(x,y)是窗口左上边开始的点。
//w,h是整个像素窗口宽和高(注意不是整个窗口)
sdlRect.x = 0;
sdlRect.y = 0;
sdlRect.w = frame->width;
sdlRect.h = frame->height;
std::cout << frame->width << " " << frame->height << std::endl;
//清空渲染器
//复制数据纹理给渲染器
//显示
SDL_RenderClear( sdlRenderer );
SDL_RenderCopy( sdlRenderer, sdlTexture, NULL, &sdlRect);
SDL_RenderPresent( sdlRenderer );
SDL_Delay(30);
}
}
SDL_DestroyWindow(window); // 推出SDL窗体
SDL_Quit(); // SDL推出
av_frame_free(&frame);
av_packet_free(&packet);
return 0;
}
2.4 编译
mac 上比较麻烦 找一堆底层依赖。
下面是CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(PlaySDL)
find_library(core_audio CoreAudio)
find_library(audio_toolbox AudioToolbox)
find_library(audio_unit AudioUnit)
find_library(core_foundation CoreFoundation)
find_library(core_services CoreServices)
set(CMAKE_CXX_STANDARD 11)
set(SDL_DIR /usr/local/Cellar/sdl2/2.0.16/) # 这个SDL开发包的路径,可以通过brew info sdl2查出来
add_executable(PlaySDL main.cpp AvDecoder.cpp AvDemux.cpp)
target_include_directories(PlaySDL PRIVATE ${SDL_DIR}/include/ ${ffmpeg_root}/include)
target_link_directories(PlaySDL PRIVATE ${SDL_DIR}/lib ${ffmpeg_root}/lib) # 增加SDL链接库目录
target_link_libraries(PlaySDL SDL2 SDL2_test SDL2main avdevice avformat avutil avcodec avfilter swscale swresample) # 链接目标库