5、AVFormat的基本使用

[TOC]ios

开始前的BB

经过上一章创建好相应的开发环境后,我们就开始撸代码了,这章简单介绍了AVFormat的解封装以及简单的应用,这章的前提知识是bash

  1. 视频文件的组成(视频轨道,音频轨道、字幕轨道等)
  2. H264文件格式(NALU)
  3. 速效救心丸

走你

咱们新建一个类微信

这个类就是写咱们今天的Demo,首先是要引入头文件网络

#include <iostream>

extern "C" {
#include <libavformat/avformat.h>
}
复制代码

使用std的命名空间,少打几个字母,没有错 我就是这么懒ide

using namespace std;
复制代码

解复用

这里咱们先来说一下解复用的流程测试

对应到代码上ui

/**
 * avformat 的简单使用
 *
 * 分离视频
 * 
 * @param url 视频的Url(本地/网络)
 */
void chapter05_h264(const char *url) {
    //打开文件流
    FILE *output = fopen("./output.h264", "wb+");

    //返回状态码
    int ret_code;
    //寻找到指定的流下标
    int media_index = -1;
    //分配一个存储解封装后的Packet
    AVPacket *packet = av_packet_alloc();

    //初始化网络
    avformat_network_init();

    //分配AVFormatContext
    AVFormatContext *avFormatContext = avformat_alloc_context();
    if (avFormatContext == nullptr) {
        cout << "[error] AVFormat alloc error" << endl;
        goto failed;
    }

    //打开输入流
    ret_code = avformat_open_input(&avFormatContext, url, nullptr, nullptr);
    if (ret_code < 0) {
        cout << "[error] open input failed " << av_err2str(AVERROR(ret_code)) << endl;
        goto failed;
    }

    //读取媒体文件信息
    ret_code = avformat_find_stream_info(avFormatContext, nullptr);
    if (ret_code < 0) {
        cout << "[error] find stream info failed " << av_err2str(AVERROR(ret_code)) << endl;
        goto failed;
    }

    //寻找到指定的视频流
    media_index = av_find_best_stream(avFormatContext, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    if (media_index < 0) {
        cout << "[error] find stream index error" << endl;
        goto failed;
    }

    //读取pakcet
    while (av_read_frame(avFormatContext, packet) == 0) {
        //判断是否是指定流的packet
        if (packet->stream_index == media_index) {
            //写入到文件
            fwrite(packet->data, 1, packet->size, output);
        }
    }


    failed:
    av_packet_free(&packet);
    avformat_close_input(&avFormatContext);

}

复制代码

main.cpp中的main方法中url

//
// Created by MirsFang on 2019-03-12.
//
#include <iostream>

extern "C"{
#include <libavformat/avformat.h>
}
#include "src/chapter_05/avformatuse.h"

using namespace std;
int main(){

    int version =avformat_version();
    cout<<"version:"<<version<<endl;
    
    const char* url = "../video/test_video.mp4";

    //分离H264
    chapter05_h264(url);

    return 0;
}
复制代码

而后咱们点击运行以后,在cmake-build-debug 目录 就会输出output.h264 文件 咱们经过命令行切换到那个目录执行 ffplay output.h264 咱们会发现什么?spa

没错 报错了! 啊哈哈哈哈 命令行

哈哈哈

这个缘由是为何的,咱们经过了解H264的文件结构知道,若是想要播放,那么每一个NALU都会有头信息以及SPS,PPS信息才能播放,因此,若是要保存的数据能播放的话,要将每一个packet都处理一下,这里会用到AVBitStreamFilter ,他的用法我在注释里面解释的比较清楚,在此就很少BB了,咱们新写一个chapter05_h264_01(url)方法

/**
 * avformat 的简单使用
 *
 * 分离视频
 * @param url 视频的Url(本地/网络)
 */
void chapter05_h264_01(const char *url) {
    //打开文件流
    FILE *output = fopen("./output_01.h264", "wb+");

    //返回状态码
    int ret_code;
    //寻找到的指定的流下标
    int media_index = -1;
    //分配一个存储读取出来的数据的 packet
    AVPacket *packet = av_packet_alloc();

    //初始化网络
    avformat_network_init();

    //建立H264_mp4的filter
    const AVBitStreamFilter *bsf = av_bsf_get_by_name("h264_mp4toannexb");
    AVBSFContext *ctx = NULL;

    //分配AVFormatContext
    AVFormatContext *avFormatContext = avformat_alloc_context();
    if (avFormatContext == nullptr) {
        cout << "[error] AVFormat alloc error" << endl;
        goto failed;
    }

    //打开输入流
    ret_code = avformat_open_input(&avFormatContext, url, nullptr, nullptr);
    if (ret_code < 0) {
        cout << "[error] open input failed " << av_err2str(AVERROR(ret_code)) << endl;
        goto failed;
    }

    //读取媒体文件信息
    ret_code = avformat_find_stream_info(avFormatContext, nullptr);
    if (ret_code < 0) {
        cout << "[error] find stream info failed " << av_err2str(AVERROR(ret_code)) << endl;
        goto failed;
    }

    //寻找到指定的视频流
    media_index = av_find_best_stream(avFormatContext, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    if (media_index < 0) {
        cout << "[error] find stream index error" << endl;
        goto failed;
    }

    //alloc bsf
    ret_code = av_bsf_alloc(bsf, &ctx);
    if (ret_code < 0) {
        cout << "[error] BSF alloc failed " << av_err2str(AVERROR(ret_code)) << endl;
        goto failed;
    }

    //复制解码器参数到BSFContext
    ret_code = avcodec_parameters_copy(ctx->par_in, avFormatContext->streams[media_index]->codecpar);
    if (ret_code < 0) {
        cout << "[error] BSF copy parameter failed " << av_err2str(AVERROR(ret_code)) << endl;
        goto failed;
    }

    //同步time_base
    ctx->time_base_in = avFormatContext->streams[media_index]->time_base;

    //初始化bsf
    ret_code = av_bsf_init(ctx);
    if (ret_code < 0) {
        cout << "[error] BSF init failed " << av_err2str(AVERROR(ret_code)) << endl;
        goto failed;
    }

    while (av_read_frame(avFormatContext, packet) == 0) {
        if (packet->stream_index != media_index)continue;
        //发送packet到BitStreamFilter
        ret_code = av_bsf_send_packet(ctx, packet);
        if (ret_code < 0) {
            cout << "[error] BSF send packet failed " << av_err2str(AVERROR(ret_code)) << endl;
            goto failed;
        }

        //接受添加sps pps头的packet
        while ((ret_code = av_bsf_receive_packet(ctx, packet)) == 0) {
            //写入到文件
            fwrite(packet->data, 1, packet->size, output);
            av_packet_unref(packet);
        }

        //须要输入数据
        if (ret_code == AVERROR(EAGAIN)) {
            cout << "[debug] BSF EAGAIN " << endl;
            av_packet_unref(packet);
            continue;
        }

        //已经读取到结尾
        if (ret_code == AVERROR_EOF) {
            cout << "[debug] BSF EOF " << endl;
            break;
        }

        if (ret_code < 0) {
            cout << "[error] BSF receive packet failed " << av_err2str(AVERROR(ret_code)) << endl;
            goto failed;
        }
    }

    //Flush
    ret_code = av_bsf_send_packet(ctx, NULL);
    if (ret_code < 0) {
        cout << "[error] BSF flush send packet failed " << av_err2str(AVERROR(ret_code)) << endl;
        goto failed;
    }

    while ((ret_code = av_bsf_receive_packet(ctx, packet)) == 0) {
        fwrite(packet->data, 1, packet->size, output);
    }

    if (ret_code != AVERROR_EOF) {
        cout << "[debug] BSF flush EOF " << endl;
        goto failed;
    }


    failed:
    //释放packet
    av_packet_free(&packet);
    //释放AVFormatContext
    avformat_close_input(&avFormatContext);
    //关闭网络流
    avformat_network_deinit();
    //释放BSFContext
    av_bsf_free(&ctx);
    //关闭文件流
    fclose(output);
}


复制代码

main方法中

#include <iostream>

extern "C"{
#include <libavformat/avformat.h>
}
#include "src/chapter_05/avformatuse.h"

using namespace std;
int main(){

    int version =avformat_version();
    cout<<"version:"<<version<<endl;

    const char* url = "../video/test_video.mp4";

    //分离H264
//    chapter05_h264(url);

    chapter05_h264_01(url);

    return 0;
}

复制代码

命令行执行ffplay output_01.h264

咱们就能够看到画面已经出来了😝

假如咱们换一个网络流(测试的时候求各位大佬不要用这个,,跑的都是个人CDN流量啊😭😭😭😭)

main方法

#include <iostream>

extern "C"{
#include <libavformat/avformat.h>
}
#include "src/chapter_05/avformatuse.h"

using namespace std;
int main(){

    int version =avformat_version();
    cout<<"version:"<<version<<endl;


    const char* httpUrl = "http://po79db9wc.bkt.clouddn.com/test_video.mp4";
    const char* url = "../video/test_video.mp4";


    //分离H264
//    chapter05_h264(url);

    chapter05_h264_01(httpUrl);

    return 0;
}
复制代码

nice !!!

这个只是带你们简单熟悉一下分离的操做,分离音频流的操做也差很少,也要先知道相应的音频流的格式(AAC),手动去写入AAC的头信息(ADIF/ADTS),你们就须要本身探讨一下了😝

若是有不理解,能够加微信群一块儿讨论

若是二维码过时,能够加我微信,备注 音视频

未完持续。。。

相关文章
相关标签/搜索