[TOC]ios
經過上一章創建好相應的開發環境後,我們就開始擼代碼了,這章簡單介紹了AVFormat的解封裝以及簡單的應用,這章的前提知識是bash
咱們新建一個類微信
這個類就是寫咱們今天的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),你們就須要本身探討一下了😝
若是有不理解,能夠加微信羣一塊兒討論
若是二維碼過時,能夠加我微信,備註 音視頻
未完持續。。。