FFmpeg 入门学习 02--音视频解封装功能实现

前言



如上图所示,在上一篇文章中介绍了视频播放的基本原理。本文继续使用 FFmpeg 来实现其中音视频解封装功能。

背景

封装格式

封装格式(也叫容器)就是将已经编码压缩好的视频流、音频流及字幕按照一定的方案放到一个文件中,便于播放软件播放。一般来说,视频文件的后缀名就是它的封装格式,如 MP4、flv 等,如下图所示:
在这里插入图片描述

解封装

解封装就是和视频封装相反的过程,即把一个流媒体文件,拆解成音频数据和视频数据,一般被拆解成 H.264 编码的视频码流和 AAC 编码的音频码流,如下图所示:
在这里插入图片描述

解封装实现流程

使用 FFmpeg 实现解封装就是在在打开视频文件后,使用 av_read_frame 接口不断读取数据包的过程,如下图所示:

在这里插入图片描述

解封装代码实现

概述

本文在之前 FFmpegPlayer 类的基础上,进一步完善相关成员变量及方法,以实现解封装功能。

完善 FFmpegPlayer 类

增加一个成员变量 packet ,用于接收和暂存读取到的数据,完善后的 FFmpegPlayer 类如下:

class FFmpegPlayer {
public:
    explicit FFmpegPlayer(const char* m_url);
    ~FFmpegPlayer();
public:
    bool        openFile();//打开文件
    void        startDecode();//开启循环解码
    void        abortDecode();//中断解码
private:
    int         readOnePacket();//读一个包
    int         readPacket();//循环读包
private:
    std::string       url;//文件路径
    AVFormatContext*  formatContext = nullptr;//封装格式上下文
    AVPacket          packet{}; //用于读包

    std::thread	*     read_thread = nullptr;//数据读取线程
    bool              abort_request{false};//强制结束
    bool              readEof{false};//读包结束

    int               video_index{-1};//视频流索引
    int               audio_index{-1};//音频流索引
};

readOnePacket 方法实现

readOnePacket 方法用于读取一个数据包,并返回是否读取成功的标记,实现如下:

int FFmpegPlayer::readOnePacket() 
{

    if( abort_request ) return -1;//响应用户中断操作

    int ret = av_read_frame( formatContext, &packet); //读取一个数据包
    if( ret < 0 )
    {
        if( ret == AVERROR_EOF )//读包结束
        {
            readEof = true;
        }
        else//出错了
        {
            return  -1;
        }
    }
    else
    {
        readEof = false;
    }
    return ret;
}

readPacket 方法实现

readPacket 方法实现了循环读包,调用 readOnePacket 读取数据包并进行处理,实现如下:

int FFmpegPlayer::readPacket() 
{
    while(true)
    {
        if( abort_request ) break;//用户退出

        std::shared_ptr<AVPacket> pktClear(&packet, [](AVPacket *p "")
        {
            av_packet_unref(p);
        });//用来确保每次 av_read_frame 后 pkt 缓存被清理了

        int ret = readOnePacket();
        if( ret == 0 )
        {
            if (packet.stream_index == video_index)
            {
                std::cout << "读取到了一个视频包,pts :" << packet.pts << std::endl;
            }
            else  if (packet.stream_index == audio_index)
            {
                std::cout << "读取到了一个音频包,pts :" << packet.pts << std::endl;
            }
        }

    }
    return 0;
}

startDecode 方法实现

创建一个线程,用于循环读包,实现如下:

void FFmpegPlayer::startDecode()
{
    read_thread = new std::thread( &FFmpegPlayer::readPacket, this  );
}

abortDecode 方法实现

用户主动中断视频播放接口,更改中断标记并结束线程,实现如下:

void FFmpegPlayer::abortDecode()
{
    abort_request = 1;

    if( read_thread && read_thread->joinable())
        read_thread->join();
}

代码运行示例

文件打开成功后,调用解复用接口,代码如下:

#include "FFmpegPlayer.h"
int main() {

    const char  * url = "C:\\Lzc\\WorkCode\\test.mkv";
    FFmpegPlayer * player = new FFmpegPlayer(url);
    if( player->openFile())
    {
        std::cout << "文件打开成功!"<<std::endl;

        player->startDecode();
    }
    
    system("pause");
    return 0;
}

运行结果如下:

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,针对您的问题,我来简单回答一下。 首先,要明确的是,FFmpeg 是一个开源的跨平台音视频处理库,支持包括码、编码、封装封装功能。而封装(Demuxing)是指将音视频封装格式中的音视频析出来,以便后续对音视频流进行码、编码等操作。 在 Android 基于 FFmpeg 开发简易播放器中,我们可以使用 FFmpeg 提供的 API 来进行封装。具体步骤如下: 1. 打开输入文件 使用 FFmpeg 的 avformat_open_input() 函数打开要封装音视频文件,该函数返回一个 AVFormatContext 结构体指针,该指针包含了输入文件的相关信息。 2. 寻找音视频流 使用 FFmpeg 的 avformat_find_stream_info() 函数读取输入文件的文件头信息,并寻找其中包含的音视频流。该函数会将每个音视频流的信息保存在 AVStream 结构体中。 3. 选择音视频流 根据需要播放的音视频流类型,在所有寻找到的音视频流中选择对应的流。可以通过判断 AVStream 结构体中的 codecpar->codec_type 来确定该流是音频流还是视频流。 4. 获取码器 使用 FFmpeg 的 avcodec_find_decoder() 函数获取对应的码器,并使用 avcodec_open2() 函数打开码器。 5. 循环读取数据包 使用 FFmpeg 的 av_read_frame() 函数循环读取音视频数据包(AVPacket 结构体),并将数据包送到码器进行码。 6. 关闭码器和输入文件 在播放完成后,需要使用 avcodec_free_context() 函数释放码器占用的资源,并使用 avformat_close_input() 函数关闭输入文件。 以上就是基于 FFmpeg 进行封装的大致步骤。当然,在实际开发中还有很多细节需要注意,比如错误处理、内存管理等。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值