【ffmpeg code】解封装和解码


最近又经常涉及到ffmpeg的使用,本来也用得不熟。现在新学开始整理一下

1. 总体步骤

打开一个媒体文件,获取帧数据统共两步:

  1. 创建一个AVFormatContext 去解封装,获取packet
  2. 通过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) # 链接目标库
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值