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),你們就須要本身探討一下了😝

若是有不理解,能夠加微信羣一塊兒討論

若是二維碼過時,能夠加我微信,備註 音視頻

未完持續。。。

相關文章
相關標籤/搜索