AVFormatContext封裝層:理論與實戰

文章目錄

  • 前言
  • 一、封裝格式簡介
    • 1、FFmpeg 中的封裝格式
    • 2、查看 FFmpeg 支持的封裝格式
  • 二、API 介紹
  • 三、 實戰 1:解封裝
    • 1、原理講解
    • 2、示例源碼 1
    • 3、運行結果 1
    • 4、示例源碼 2
    • 5、運行結果 2
  • 四、 實戰 2:轉封裝
    • 1、原理講解
    • 2、示例源碼
    • 3、運行結果


前言

AVFormatContext 是一個貫穿始終的數據結構,很多函數都用到它作為參數,是輸入輸出相關信息的一個容器,本文講解 AVFormatContext 的封裝層,主要包括兩大數據結構:AVInputFormat,AVOutputFormat
在這里插入圖片描述


一、封裝格式簡介

封裝格式(container format)可以看作是編碼流(音頻流、視頻流等)數據的一層外殼,將編碼后的數據存儲于此封裝格式的文件之內。

封裝又稱容器,容器的稱法更為形象,所謂容器,就是存放內容的器具,例如飲料是內容,那么裝飲料的瓶子就是容器。

不同封裝格式適用于不同的場合,支持的編碼格式不一樣,幾個常用的封裝格式如下:
在這里插入圖片描述

1、FFmpeg 中的封裝格式

FFmpeg 關于封裝格式的處理涉及打開輸入文件、打開輸出文件、從輸入文件讀取編碼幀、往輸出文件寫入編碼幀這幾個步驟,這些都不涉及編碼解碼層面。

在 FFmpeg 中,mux 指復用,是 multiplex 的縮寫,表示將多路流(視頻、音頻、字幕等)混入一路輸出中(普通文件、流等)。demux 指解復用,是 mux 的反操作,表示從一路輸入中分離出多路流(視頻、音頻、字幕等)。

mux 處理的是輸入格式,demux 處理的輸出格式。輸入/輸出媒體格式涉及文件格式和封裝格式兩個概念【封裝格式】 。

  • 文件格式由文件擴展名標識【文件格式】,主要起提示作用,通過擴展名提示文件類型(或封裝格式)信息。封裝格式則是存儲媒體內容的實際容器格式,不同的封裝格式對應不同的文件擴展名,很多時候也用文件格式代指封裝格式,例如常用 ts 格式(文件格式)代指 mpegts 格式(封裝格式)。
    • 例如,我們把 test.ts 改名為 test.mkv,mkv 擴展名提示了此文件封裝格式為 Matroska,但文件內容并無任何變化,使用 ffprobe 工具仍能正確探測出封裝格式為 mpegts。

2、查看 FFmpeg 支持的封裝格式

使用 ffmpeg -formats 命令可以查看 FFmpeg 支持的封裝格式。 FFmpeg 支持的封裝非常多, 下面僅列出最常用的幾種:

  • h264/aac 裸流封裝格式
    • h264 裸流封裝格式和 aac 裸流封裝格式在后面的解復用和復用例程中會用到,這里先討論一下。h264 本來是編碼格式,當作封裝格式時表示的是 H.264 裸流格式,所謂裸流就是不含封裝信息的流,也就是沒穿衣服的流。aac 等封裝格式類似。

看一下 FFmpeg 工程源碼中 h264 編碼格式以及 h264 封裝格式的定義:FFmpeg 工程包含 h264 解碼器,而不包含 h264 編碼器(一般使用第三方 libx264 編碼器用作 h264 編碼),所以只有解碼器定義:

AVCodec ff_h264_decoder = {.name = "h264",.long_name = NULL_IF_CONFIG_SMALL("H.264 / AVC / MPEG-4 AVC /MPEG-4 part 10"),.type = AVMEDIA_TYPE_VIDEO,.id = AV_CODEC_ID_H264,......
};

h264 封裝格式定義如下:

AVOutputFormat ff_h264_muxer = {.name = "h264",.long_name = NULL_IF_CONFIG_SMALL("raw H.264 video"),.extensions = "h264,264",.audio_codec = AV_CODEC_ID_NONE,.video_codec = AV_CODEC_ID_H264,.write_header = force_one_stream,.write_packet = ff_raw_write_packet,.check_bitstream = h264_check_bitstream,.flags = AVFMT_NOTIMESTAMPS,
};AVOutputFormat ff_h264_muxer = {.name = "h264",.long_name = NULL_IF_CONFIG_SMALL("raw H.264 video"),.extensions = "h264,264",.audio_codec = AV_CODEC_ID_NONE,.video_codec = AV_CODEC_ID_H264,.write_header = force_one_stream,.write_packet = ff_raw_write_packet,.check_bitstream = h264_check_bitstream,.flags = AVFMT_NOTIMESTAMPS,
};

二、API 介紹

FFmpeg 中將編碼幀未編碼幀均稱作 frame,本文為方便,將編碼幀稱作 packet未編碼幀稱作 frame

未壓縮的,未編碼的:原始圖片(yuv,rgb)或聲音(pcm 流)。
壓縮的, 編碼的:H264/265,aac/ac3/mp3

最主要的 API 有如下幾個:

  • avformat_open_input():這個函數會打開輸入媒體文件,讀取文件頭,將文件格式信息存儲在第一個參數 AVFormatContext 中。
  • avformat_find_stream_info():這個函數會讀取一段視頻文件數據并嘗試解碼,將取到的流信息填入 AVFormatContext.streams 中。AVFormatContext.streams 是一個指針數組,數組大小是 AVFormatContext.nb_streams
  • av_read_frame():本函數用于解復用過程。本函數將存儲在輸入文件中的數據分割為多個 packet, 每次調用將得到一個 packet。 packet 可能是視頻幀、音頻幀或其他數據,解碼器只會解碼視頻幀或音頻幀,非音視頻數據并不會
    被扔掉、從而能向解碼器提供盡可能多的信息。
    • 對于視頻來說,一個 packet 只包含一個視頻幀;
    • 對于音頻來說,若是幀長固定的格式則一個 packet 可包含整數個音頻幀,若是幀長可變的格式則一個 packet 只包含一個音頻幀。
    • 讀取到的 packet 每次使用完之后應調用 av_packet_unref(AVPacket *pkt) 清空 packet。否則會造成內存泄露。
  • av_write_frame():本函數用于復用過程,將 packet 寫入輸出媒體。
    • packet 交織是指:不同流的 packet 在輸出媒體文件中應嚴格按照 packet 中 dts 遞增的順序交錯存放。
    • 本函數直接將 packet 寫入復用器(muxer),不會緩存或記錄任何 packet。本函數不負責不同流的 packet 交織問題。,由調用者負責。
    • 如果調用者不愿處理 packet 交織問題,應調用 av_interleaved_write_frame() 替代本函數。
  • av_interleaved_write_frame():本函數用于復用過程, 將 packet 寫入輸出媒體。
    • 本函數將按需在內部緩存 packet,從而確保輸出媒體中不同流的 packet 能按照 dts 增長的順序正確交織。
  • avio_open() :創建并初始化一個 AVIOContext,用于訪問輸出媒體文件。
  • avformat_write_header():向輸出文件寫入文件頭信息。
  • av_write_trailer():向輸出文件寫入文件尾信息。

三、 實戰 1:解封裝

1、原理講解

本例子實現的是將音視頻分離,例如將封裝格式為 FLV、MKV、MP4、AVI 等封裝格式的文件,將音頻、視頻讀取出來并打印。實現的過程,可以大致用如下圖表示:
在這里插入圖片描述

2、示例源碼 1

兼容舊版本使用遍歷的方式查找給定媒體文件中音頻流或視頻流,未使用新版本的 FFmpeg 新增加的函數 av_find_best_stream()

#include <stdio.h>
extern "C" {#include <libavformat/avformat.h>
}/**
* @brief 將一個AVRational類型的分數轉換為double類型的浮點數
* @param r:r為一個AVRational類型的結構體變量,成員num表示分子,成員den表示分母,r的值即為(double)r.num / (double)r.den。用這種方法表示可以最大程度地避免精度的損失
* @return 如果變量r的分母den為0,則返回0(為了避免除數為0導致程序死掉);其余情況返回(double)r.num / (double)r.den
*/
static double r2d(AVRational r)
{return r.den == 0 ? 0 : (double)r.num / (double)r.den;
}int main()
{//需要讀取的本地媒體文件相對路徑為 ./debug/test.mp4const char *path = "./debug/test.mp4";///av_register_all(); //初始化所有組件,只有調用了該函數,才能使用復用器和編解碼器。否則,調用函數avformat_open_input會失敗,無法獲取媒體文件的信息avformat_network_init(); //打開網絡流。這里如果只需要讀取本地媒體文件,不需要用到網絡功能,可以不用加上這一句AVDictionary *opts = NULL;//AVFormatContext是描述一個媒體文件或媒體流的構成和基本信息的結構體AVFormatContext *ic = NULL;//媒體打開函數,調用該函數可以獲得路徑為path的媒體文件的信息,并把這些信息保存到指針ic指向的空間中(調用該函數后會分配一個空間,讓指針ic指向該空間)int re = avformat_open_input(&ic, path, NULL, &opts);if (re != 0)  //如果打開媒體文件失敗,打印失敗原因。比如,如果上面沒有調用函數av_register_all,則會打印“XXX failed!:Invaliddata found when processing input”{char buf[1024] = { 0 };av_strerror(re, buf, sizeof(buf) - 1);printf("open %s failed!:%s", path, buf);}else         //打開媒體文件成功{printf("打開媒體文件 %s 成功!\n", path);//調用該函數可以進一步讀取一部分視音頻數據并且獲得一些相關的信息。//調用avformat_open_input之后,我們無法獲取到正確和所有的媒體參數,所以還得要調用avformat_find_stream_info進一步的去獲取。avformat_find_stream_info(ic, NULL);//調用avformat_open_input讀取到的媒體文件的路徑/名字printf("媒體文件名稱:%s\n", ic->filename);//視音頻流的個數,如果一個媒體文件既有音頻,又有視頻,則nb_streams的值為2。如果媒體文件只有音頻,則值為1printf("視音頻流的個數:%d\n", ic->nb_streams);//媒體文件的平均碼率,單位為bpsprintf("媒體文件的平均碼率:%lldbps\n", ic->bit_rate);printf("duration:%d\n", ic->duration);int tns, thh, tmm, tss;tns = (ic->duration) / AV_TIME_BASE;thh = tns / 3600;tmm = (tns % 3600) / 60;tss = (tns % 60);printf("媒體文件總時長:%d時%d分%d秒\n", thh, tmm, tss); //通過上述運算,可以得到媒體文件的總時長printf("\n");//通過遍歷的方式讀取媒體文件視頻和音頻的信息,//新版本的FFmpeg新增加了函數av_find_best_stream,也可以取得同樣的效果,但這里為了兼容舊版本還是用這種遍歷的方式for (int i = 0; i < ic->nb_streams; i++){AVStream *as = ic->streams[i];if (AVMEDIA_TYPE_AUDIO == as->codecpar->codec_type) //如果是音頻流,則打印音頻的信息{printf("音頻信息:\n");printf("index:%d\n", as->index);  //如果一個媒體文件既有音頻,又有視頻,則音頻index的值一般為1。但該值不一定準確,所以還是得通過as->codecpar->codec_type判斷是視頻還是音頻printf("音頻采樣率:%dHz\n", as->codecpar->sample_rate); //音頻編解碼器的采樣率,單位為Hzif (AV_SAMPLE_FMT_FLTP == as->codecpar->format)   //音頻采樣格式{printf("音頻采樣格式:AV_SAMPLE_FMT_FLTP\n");}else if (AV_SAMPLE_FMT_S16P == as->codecpar->format){printf("音頻采樣格式:AV_SAMPLE_FMT_S16P\n");}printf("音頻信道數目:%d\n", as->codecpar->channels); //音頻信道數目if (AV_CODEC_ID_AAC == as->codecpar->codec_id)   //音頻壓縮編碼格式{printf("音頻壓縮編碼格式:AAC\n");}else if (AV_CODEC_ID_MP3 == as->codecpar->codec_id){printf("音頻壓縮編碼格式:MP3\n");}int DurationAudio = (as->duration) * r2d(as->time_base); //音頻總時長,單位為秒。注意如果把單位放大為毫秒或者微妙,音頻總時長跟視頻總時長不一定相等的printf("音頻總時長:%d時%d分%d秒\n", DurationAudio / 3600, (DurationAudio % 3600) / 60, (DurationAudio % 60)); //將音頻總時長轉換為時分秒的格式打印到控制臺上printf("\n");}else if (AVMEDIA_TYPE_VIDEO == as->codecpar->codec_type)  //如果是視頻流,則打印視頻的信息{printf("視頻信息:\n");printf("index:%d\n", as->index);   //如果一個媒體文件既有音頻,又有視頻,則視頻index的值一般為0。但該值不一定準確,所以還是得通過as->codecpar->codec_type判斷是視頻還是音頻printf("視頻幀率:%lffps\n", r2d(as->avg_frame_rate)); //視頻幀率,單位為fps,表示每秒出現多少幀if (AV_CODEC_ID_MPEG4 == as->codecpar->codec_id) //視頻壓縮編碼格式{printf("視頻壓縮編碼格式:MPEG4\n");}printf("幀寬度:%d 幀高度:%d\n", as->codecpar->width, as->codecpar->height); //視頻幀寬度和幀高度int DurationVideo = (as->duration) * r2d(as->time_base); //視頻總時長,單位為秒。注意如果把單位放大為毫秒或者微妙,音頻總時長跟視頻總時長不一定相等的printf("視頻總時長:%d時%d分%d秒\n", DurationVideo / 3600, (DurationVideo % 3600) / 60, (DurationVideo % 60)); //將視頻總時長轉換為時分秒的格式打印到控制臺上printf("\n");}}//av_dump_format(ic, 0, path, 0);}if (ic){avformat_close_input(&ic); //關閉一個AVFormatContext,和函數avformat_open_input()成對使用}avformat_network_deinit();return 0;
}

3、運行結果 1

打開媒體文件 ./debug/test.mp4 成功!
媒體文件名稱:./debug/test.mp4
視音頻流的個數:2
媒體文件的平均碼率:1436830bps
duration:117312000
媒體文件總時長:0時1分57秒視頻信息:
index:0
視頻幀率:25.000000fps
幀寬度:1280 幀高度:720
視頻總時長:0時1分57秒音頻信息:
index:1
音頻采樣率:48000Hz
音頻采樣格式:AV_SAMPLE_FMT_FLTP
音頻信道數目:6
音頻壓縮編碼格式:AAC
音頻總時長:0時1分57秒

使用 MediaInfo 打開分析可以看到與上面的打印信息是對應上的
在這里插入圖片描述

4、示例源碼 2

使用新版本的 FFmpeg 新增加的函數 av_find_best_stream() 查找給定媒體文件中最佳的流(音頻流或視頻流)

#include <stdio.h>
extern "C"
{#include <libavformat/avformat.h>
}
int main(int argc, char **argv)
{
// 1. 打開文件const char *ifilename = "./debug/test.mp4";printf("in_filename = %s\n", ifilename);avformat_network_init();// AVFormatContext是描述一個媒體文件或媒體流的構成和基本信息的結構體AVFormatContext *ifmt_ctx = NULL;           // 輸入文件的demux// 打開文件,主要是探測協議類型,如果是網絡文件則創建網絡鏈接int ret = avformat_open_input(&ifmt_ctx, ifilename, NULL, NULL);if (ret < 0) {char buf[1024] = {0};av_strerror(ret, buf, sizeof (buf) - 1);printf("open %s failed: %s\n", ifilename, buf);return -1;}// 2. 讀取碼流信息ret = avformat_find_stream_info(ifmt_ctx, NULL);if (ret < 0)  //如果打開媒體文件失敗,打印失敗原因{char buf[1024] = { 0 };av_strerror(ret, buf, sizeof(buf) - 1);printf("avformat_find_stream_info %s failed:%s\n", ifilename, buf);avformat_close_input(&ifmt_ctx);return -1;}// 3.打印總體信息printf_s("\n==== av_dump_format in_filename:%s ===\n", ifilename);av_dump_format(ifmt_ctx, 0, ifilename, 0);printf_s("\n==== av_dump_format finish =======\n\n");printf("media name:%s\n", ifmt_ctx->url);printf("stream number:%d\n", ifmt_ctx->nb_streams); //  nb_streams媒體流數量printf("media average ratio:%lldkbps\n",(int64_t)(ifmt_ctx->bit_rate/1024)); //  媒體文件的碼率,單位為bps/1000=Kbps// duration: 媒體文件時長,單位微妙int total_seconds = (ifmt_ctx->duration) / AV_TIME_BASE;  // 1000us = 1ms, 1000ms = 1秒printf("audio duration: %02d:%02d:%02d\n",total_seconds / 3600, (total_seconds % 3600) / 60, (total_seconds % 60));printf("\n");// 4.讀取碼流信息// 音頻int audioindex = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);if (audioindex < 0) {printf("av_find_best_stream %s eror.", av_get_media_type_string(AVMEDIA_TYPE_AUDIO));return -1;}AVStream *audio_stream = ifmt_ctx->streams[audioindex];printf("----- Audio info:\n");printf("index: %d\n", audio_stream->index); // 序列號printf("samplarate: %d Hz\n", audio_stream->codecpar->sample_rate); // 采樣率printf("sampleformat: %d\n", audio_stream->codecpar->format); // 采樣格式 AV_SAMPLE_FMT_FLTP:8printf("audio codec: %d\n", audio_stream->codecpar->codec_id); // 編碼格式 AV_CODEC_ID_MP3:86017 AV_CODEC_ID_AAC:86018if (audio_stream->duration != AV_NOPTS_VALUE) {int audio_duration = audio_stream->duration * av_q2d(audio_stream->time_base);printf("audio duration: %02d:%02d:%02d\n",audio_duration / 3600, (audio_duration % 3600) / 60, (audio_duration % 60));}// 視頻int videoindex = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);if (videoindex < 0) {printf("av_find_best_stream %s eror.", av_get_media_type_string(AVMEDIA_TYPE_VIDEO));return -1;}AVStream *video_stream = ifmt_ctx->streams[videoindex];printf("----- Video info:\n");printf("index: %d\n", video_stream->index); // 序列號printf("fps: %lf\n", av_q2d(video_stream->avg_frame_rate)); // 幀率printf("width: %d, height:%d \n", video_stream->codecpar->width, video_stream->codecpar->height);printf("video codec: %d\n", video_stream->codecpar->codec_id); // 編碼格式 AV_CODEC_ID_H264: 27if (video_stream->duration != AV_NOPTS_VALUE) {int video_duration = video_stream->duration * av_q2d(video_stream->time_base);printf("audio duration: %02d:%02d:%02d\n",video_duration / 3600, (video_duration % 3600) / 60, (video_duration % 60));}// 5.提取碼流AVPacket *pkt = av_packet_alloc();int pkt_count = 0;int print_max_count = 100;printf("\n-----av_read_frame start\n");while (1){ret = av_read_frame(ifmt_ctx, pkt);if (ret < 0) {printf("av_read_frame end\n");break;}if(pkt_count++ < print_max_count){if (pkt->stream_index == audioindex){printf("audio pts: %lld\n", pkt->pts);printf("audio dts: %lld\n", pkt->dts);printf("audio size: %d\n", pkt->size);printf("audio pos: %lld\n", pkt->pos);printf("audio duration: %lf\n\n",pkt->duration * av_q2d(ifmt_ctx->streams[audioindex]->time_base));}else if (pkt->stream_index == videoindex){printf("video pts: %lld\n", pkt->pts);printf("video dts: %lld\n", pkt->dts);printf("video size: %d\n", pkt->size);printf("video pos: %lld\n", pkt->pos);printf("video duration: %lf\n\n",pkt->duration * av_q2d(ifmt_ctx->streams[videoindex]->time_base));}else{printf("unknown stream_index:\n", pkt->stream_index);}}av_packet_unref(pkt);}// 6.結束if(pkt)av_packet_free(&pkt);if(ifmt_ctx)avformat_close_input(&ifmt_ctx);avformat_network_deinit();return 0;
}

5、運行結果 2

in_filename = ./debug/test.mp4==== av_dump_format in_filename:./debug/test.mp4 ======= av_dump_format finish =======media name:./debug/test.mp4
stream number:2
media average ratio:1403kbps
audio duration: 00:01:57----- Audio info:
index: 1
samplarate: 48000 Hz
sampleformat: 8
audio codec: 86018
audio duration: 00:01:57
----- Video info:
index: 0
fps: 25.000000
width: 1280, height:720 
video codec: 27
audio duration: 00:01:57-----av_read_frame start
audio pts: 0
audio dts: 0
audio size: 967
audio pos: 48
audio duration: 0.021333video pts: 0
video dts: 0
video size: 105222
video pos: 1015
video duration: 0.040000
....
audio pts: 65536
audio dts: 65536
audio size: 949
audio pos: 346354
audio duration: 0.021333video pts: 17408
video dts: 17408
video size: 6906
video pos: 347303
video duration: 0.040000
av_read_frame end
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from './debug/test.mp4':Metadata:major_brand     : isomminor_version   : 512compatible_brands: isomiso2avc1mp41creation_time   : 1970-01-01T00:00:00.000000Zencoder         : Lavf53.24.2Duration: 00:01:57.31, start: 0.000000, bitrate: 1436 kb/sStream #0:0(und): Video: h264 (Main) (avc1 / 0x31637661), yuv420p, 1280x720 [SAR 1:1 DAR 16:9], 1048 kb/s, 25 fps, 25 tbr, 12800 tbn, 50 tbc (default)Metadata:creation_time   : 1970-01-01T00:00:00.000000Zhandler_name    : VideoHandlerStream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, 5.1, fltp, 383 kb/s (default)Metadata:creation_time   : 1970-01-01T00:00:00.000000Zhandler_name    : SoundHandler

四、 實戰 2:轉封裝

1、原理講解

從一種視頻容器轉成另一種視頻容器。

所謂的封裝格式轉換,就是在 AVI,FLV,MKV,MP4 這些格式之間轉換(對應 .avi.flv.mkv.mp4 文件)。需要注意的是,本程序并不進行視音頻的編碼和解碼工作。而是直接將視音頻壓縮碼流從一種封裝格式文件中獲取出來然后打包成另外一種封裝格式的文件。
在這里插入圖片描述
傳統的轉碼程序工作原理如下圖所示:
在這里插入圖片描述
上圖例舉了一個舉例:FLV(視頻:H.264,音頻:AAC)轉碼為 AVI(視頻:MPEG2,音頻 MP3)的例子。可見視頻轉碼的過程通俗地講相當于把視頻和音頻重新“錄” 了一遍。

本程序的工作原理如下圖所示:
在這里插入圖片描述
由圖可見,本程序并不進行視頻和音頻的編解碼工作,因此本程序和普通的轉碼軟件相比,有以下兩個特點:

  • 處理速度極快。視音頻編解碼算法十分復雜,占據了轉碼的絕大部分時間。因為不需要進行視音頻的編碼和解碼,所以節約了大量的時間。
  • 視音頻質量無損。
  • 因為不需要進行視音頻的編碼和解碼, 所以不會有視音頻的壓縮損傷。

程序流程圖如下圖:
在這里插入圖片描述

2、示例源碼

以下源碼實現下面命令同樣的功能

ffmepg -i test.mp4 -c copy -f test.flv
#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>static void log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt, const char *tag)
{AVRational *time_base = &fmt_ctx->streams[pkt->stream_index]->time_base;printf("%s: pts:%s pts_time:%s dts:%s dts_time:%s duration:%s duration_time:%s stream_index:%d\n",tag,av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, time_base),av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, time_base),av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, time_base),pkt->stream_index);
}int main(int argc, char **argv)
{AVOutputFormat *ofmt = NULL;AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;AVPacket pkt;const char *in_filename, *out_filename;int ret, i;int stream_index = 0;int *stream_mapping = NULL;int stream_mapping_size = 0;in_filename  = "./debug/test.mp4";out_filename = "./debug/test.flv";if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {fprintf(stderr, "Could not open input file '%s'", in_filename);goto end;}if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {fprintf(stderr, "Failed to retrieve input stream information");goto end;}printf("\n\n-------av_dump_format:ifmt_ctx----------------\n");av_dump_format(ifmt_ctx, 0, in_filename, 0);avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);if (!ofmt_ctx) {fprintf(stderr, "Could not create output context\n");ret = AVERROR_UNKNOWN;goto end;}stream_mapping_size = ifmt_ctx->nb_streams;stream_mapping = av_mallocz_array(stream_mapping_size, sizeof(*stream_mapping));if (!stream_mapping) {ret = AVERROR(ENOMEM);goto end;}ofmt = ofmt_ctx->oformat;for (i = 0; i < ifmt_ctx->nb_streams; i++) {AVStream *out_stream;AVStream *in_stream = ifmt_ctx->streams[i];AVCodecParameters *in_codecpar = in_stream->codecpar;if (in_codecpar->codec_type != AVMEDIA_TYPE_AUDIO &&in_codecpar->codec_type != AVMEDIA_TYPE_VIDEO &&in_codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) {stream_mapping[i] = -1;continue;}stream_mapping[i] = stream_index++;out_stream = avformat_new_stream(ofmt_ctx, NULL);if (!out_stream) {fprintf(stderr, "Failed allocating output stream\n");ret = AVERROR_UNKNOWN;goto end;}ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar);if (ret < 0) {fprintf(stderr, "Failed to copy codec parameters\n");goto end;}out_stream->codecpar->codec_tag = 0;}printf("\n\n-------av_dump_format:ofmt_ctx----------------\n");av_dump_format(ofmt_ctx, 0, out_filename, 1);if (!(ofmt->flags & AVFMT_NOFILE)) {ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);if (ret < 0) {fprintf(stderr, "Could not open output file '%s'", out_filename);goto end;}}ret = avformat_write_header(ofmt_ctx, NULL);if (ret < 0) {fprintf(stderr, "Error occurred when opening output file\n");goto end;}while (1) {AVStream *in_stream, *out_stream;/// 讀取音視頻壓縮包ret = av_read_frame(ifmt_ctx, &pkt);if (ret < 0)break;in_stream  = ifmt_ctx->streams[pkt.stream_index];if (pkt.stream_index >= stream_mapping_size ||stream_mapping[pkt.stream_index] < 0) {av_packet_unref(&pkt);continue;}pkt.stream_index = stream_mapping[pkt.stream_index];out_stream = ofmt_ctx->streams[pkt.stream_index];log_packet(ifmt_ctx, &pkt, "in");/* copy packet */pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);pkt.pos = -1;log_packet(ofmt_ctx, &pkt, "out");/// 交織寫音視頻包ret = av_interleaved_write_frame(ofmt_ctx, &pkt);if (ret < 0) {fprintf(stderr, "Error muxing packet\n");break;}av_packet_unref(&pkt);//包需要解引用}av_write_trailer(ofmt_ctx);
end:avformat_close_input(&ifmt_ctx);/* close output */if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))avio_closep(&ofmt_ctx->pb);avformat_free_context(ofmt_ctx);av_freep(&stream_mapping);if (ret < 0 && ret != AVERROR_EOF) {fprintf(stderr, "Error occurred: %s\n", av_err2str(ret));return 1;}return 0;
}

3、運行結果

-------av_dump_format:ifmt_ctx-----------------------av_dump_format:ofmt_ctx----------------
in: pts:0 pts_time:0 dts:0 dts_time:0 duration:1024 duration_time:0.0213333 stream_index:1
out: pts:0 pts_time:0 dts:0 dts_time:0 duration:21 duration_time:0.021 stream_index:1
in: pts:0 pts_time:0 dts:0 dts_time:0 duration:512 duration_time:0.04 stream_index:0
out: pts:0 pts_time:0 dts:0 dts_time:0 duration:40 duration_time:0.04 stream_index:0
in: pts:1024 pts_time:0.0213333 dts:1024 dts_time:0.0213333 duration:1024 duration_time:0.0213333 stream_index:1
out: pts:21 pts_time:0.021 dts:21 dts_time:0.021 duration:21 duration_time:0.021 stream_index:1
....
in: pts:1500672 pts_time:117.24 dts:1500672 dts_time:117.24 duration:512 duration_time:0.04 stream_index:0
out: pts:117240 pts_time:117.24 dts:117240 dts_time:117.24 duration:40 duration_time:0.04 stream_index:0
in: pts:5628928 pts_time:117.269 dts:5628928 dts_time:117.269 duration:1024 duration_time:0.0213333 stream_index:1
out: pts:117269 pts_time:117.269 dts:117269 dts_time:117.269 duration:21 duration_time:0.021 stream_index:1
in: pts:5629952 pts_time:117.291 dts:5629952 dts_time:117.291 duration:1024 duration_time:0.0213333 stream_index:1
out: pts:117291 pts_time:117.291 dts:117291 dts_time:117.291 duration:21 duration_time:0.021 stream_index:1
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from './debug/test.mp4':Metadata:major_brand     : isomminor_version   : 512compatible_brands: isomiso2avc1mp41creation_time   : 1970-01-01T00:00:00.000000Zencoder         : Lavf53.24.2Duration: 00:01:57.31, start: 0.000000, bitrate: 1436 kb/sStream #0:0(und): Video: h264 (Main) (avc1 / 0x31637661), yuv420p, 1280x720 [SAR 1:1 DAR 16:9], 1048 kb/s, 25 fps, 25 tbr, 12800 tbn, 50 tbc (default)Metadata:creation_time   : 1970-01-01T00:00:00.000000Zhandler_name    : VideoHandlerStream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, 5.1, fltp, 383 kb/s (default)Metadata:creation_time   : 1970-01-01T00:00:00.000000Zhandler_name    : SoundHandler
Output #0, flv, to './debug/test.flv':Stream #0:0: Video: h264 (Main), yuv420p, 1280x720 [SAR 1:1 DAR 16:9], q=2-31, 1048 kb/sStream #0:1: Audio: aac (LC), 48000 Hz, 5.1, fltp, 383 kb/s

使用 MediaInfo 查看未進行轉封裝前 test.mp4 的封裝格式為 MPEG-4
在這里插入圖片描述

使用 MediaInfo 查看轉封裝后新生成的 test.flv 文件的封裝格式為 Flash Video
在這里插入圖片描述


我的qq:2442391036,歡迎交流!


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/211818.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/211818.shtml
英文地址,請注明出處:http://en.pswp.cn/news/211818.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

文章解讀與仿真程序復現思路——電力系統自動化EI\CSCD\北大核心《考慮電力-交通交互的配電網故障下電動汽車充電演化特性》

這個標題涉及到電力系統、交通系統和電動汽車充電的復雜主題。讓我們逐步解讀&#xff1a; 考慮電力-交通交互的配電網故障&#xff1a; 電力-交通交互&#xff1a; 指的是電力系統和交通系統之間相互影響、相互關聯的關系。這可能涉及到電力需求對交通流量的影響&#xff0c;反…

回溯算法之N皇后

一 什么是回溯算法 回溯算法&#xff08;Backtracking Algorithm&#xff09;是一種用于解決組合優化問題的算法&#xff0c;它通過逐步構建候選解并進行驗證&#xff0c;以尋找所有滿足特定條件的解。回溯算法通常應用于在給定約束條件下枚舉所有可能解的問題&#xff0c;如…

Git—文件添加查看刪除修改

目錄 1.添加文件—場景一 2.查看.git文件 3.添加文件—場景三 4.修改文件 5.版本回退 6.撤銷修改 7.刪除文件 1.添加文件—場景一 在包含.git的目錄下新建?個ReadMe文件&#xff0c;我們可以使用 git add 命令可以將文件添加到暫存 區&#xff1a; ●添加一個或多個文…

Matlab數學建模算法之小波神經網絡詳解

&#x1f517; 運行環境&#xff1a;Matlab &#x1f6a9; 撰寫作者&#xff1a;左手の明天 &#x1f947; 精選專欄&#xff1a;《python》 &#x1f525; 推薦專欄&#xff1a;《算法研究》 &#x1f510;#### 防偽水印——左手の明天 ####&#x1f510; &#x1f497; 大家…

vue的屬性

key 預期&#xff1a;number | string | boolean (2.4.2 新增) | symbol (2.5.12 新增) key 的特殊 attribute 主要用在 Vue 的虛擬 DOM 算法&#xff0c;在新舊 nodes 對比時辨識 VNodes。如果不使用 key&#xff0c;Vue 會使用一種最大限度減少動態元素并且盡可能的嘗試就地…

2022藍橋杯c組求和

題目名字 求和 題目鏈接 題意 輸入的每個數都要兩兩相乘&#xff0c;然后再加起來&#xff0c;求最后總和&#xff1b; 思路 每個數乘這個數的前綴和即可 算法一&#xff1a;前綴和 實現步驟 先把前綴和寫出來再寫for循環每個數都乘以自己的前綴和&#xff1b; 實現步驟 直接…

存儲成本降71%,怪獸充電歷史庫遷移OceanBase

怪獸充電作為共享充電寶第一股&#xff0c;業務增長迅速&#xff0c;以至于業務架構不停地增加組件。在驗證 OceanBase 可以簡化架構并帶來更大的業務價值后&#xff0c;首次嘗試在歷史庫中使用 OceanBase 替代 MySQL&#xff0c;存儲成本降低 71%。本文為怪獸充電運維架構部王…

Docker 入門

Docker 入門 基礎 不同操作系統下其安裝包、運行環境是都不相同的&#xff01;如果是手動安裝&#xff0c;必須手動解決安裝包不同、環境不同的、配置不同的問題 而使用Docker&#xff0c;這些完全不用考慮。就是因為Docker會自動搜索并下載MySQL。注意&#xff1a;這里下載…

【C++】輸入輸出流 ⑥ ( cout 標準輸出流對象 | cout 常用 api 簡介 | cout.put(char c) 函數 )

文章目錄 一、cout 標準輸出流對象1、cout 標準輸出流對象簡介2、cout 常用 api 簡介 二、cout.put(char c) 函數1、cout.put(char c) 函數 簡介2、代碼示例 - cout.put(char c) 函數 一、cout 標準輸出流對象 1、cout 標準輸出流對象簡介 cout 是 標準輸出流 對象 , 是 ostrea…

端口被占用 --- 解決方案

問題描述 加速服務啟動失敗&#xff0c;443端口被magentproc(1576)占用。請關掉占用443端口的程序或者嘗試使用系統代理模式。 問題解決 按下 win R 打開 輸入cmd 輸入命令 netstat -ano | findstr 443 找到 0.0.0.0:443 對應的端口 (1576) 按下 ctrl shift esc, 打開任務管…

綜述 2023-IEEE-TCBB:生物序列聚類方法比較

Wei, Ze-Gang, et al. "Comparison of methods for biological sequence clustering." IEEE/ACM Transactions on Computational Biology and Bioinformatics (2023). https://ieeexplore.ieee.org/document/10066180 被引次數&#xff1a;1&#xff1b;研究背景&am…

力扣題:數字與字符串間轉換-12.13

力扣題-12.13 [力扣刷題攻略] Re&#xff1a;從零開始的力扣刷題生活 力扣題1&#xff1a;442. 數組中重復的數據 解題思想&#xff1a;直接相除即可 class Solution(object):def optimalDivision(self, nums):""":type nums: List[int]:rtype: str"&qu…

Transformer 簡介

Transformer 是 Google 在 2017 年底發表的論文 Attention Is All You Need 中所提出的 seq2seq 模型。Transformer 模型的核心是 Self-Attention 機制&#xff0c;能夠處理輸入序列中的每個元素&#xff0c;并能計算其與序列中其他元素的交互關系的方法&#xff0c;從而能夠更…

再見了Future,圖解JDK21虛擬線程的結構化并發

Java為我們提供了許多啟動線程和管理線程的方法。在本文中&#xff0c;我們將介紹一些在Java中進行并發編程的選項。我們將介紹結構化并發的概念&#xff0c;然后討論Java 21中一組預覽類——它使將任務拆分為子任務、收集結果并對其進行操作變得非常容易&#xff0c;而且不會不…

Unity中Shader黑白閥值后處理效果

文章目錄 前言一、我們先來PS看一下黑白閥值的效果二、使用step(a,b)函數實現效果三、實現腳本控制黑白閥值1、在Shader屬性面板定義控制閥值變量2、把step的a改為_Value3、在后處理腳本設置公共成員變量,并且設置范圍為&#xff08;0&#xff0c;1&#xff09;4、在Graphics.B…

Cocos Creator:創建棋盤

Cocos Creator&#xff1a;創建棋盤 創建地圖三部曲&#xff1a;1. 創建layout組件2. 創建預制體Prefab&#xff0c;做好精靈貼圖&#xff1a;3. 創建腳本LayoutSprite.ts收尾工作&#xff1a; 創建地圖三部曲&#xff1a; 1. 創建layout組件 使用layout進行布局&#xff0c;…

優化瑞芯微rk3566 tf卡速度uhs SDR104

環境 開發板&#xff1a;orangepi3B CPU:rk3566 TF卡速度標識&#xff1a;C10&#xff0c;U3&#xff0c;V30 起因 對于tf卡啟動的系統來說&#xff0c;io會成為一個很關鍵的瓶頸&#xff0c;所以總希望系統能跑得快一點。我手頭用的是一張金士頓的高性能tf卡&#xff0c;開…

四十三、Redis基礎

目錄 一、認識NoSql 1、定義&#xff1a; 2、常見語法 3、與關系型數據庫&#xff08;SQL&#xff09;的區別&#xff1a; 二、認識Redis 1、定義&#xff1a; 2、特征&#xff1a; 3、Key的結構&#xff1a; 三、安裝Redis 四、Redis常見命令 1、數據結構介紹 2、…

關于DNS服務器地址總是127.0.0.1且無法解析域名地址

問題 筆者嘗試nslookup解釋域名時&#xff0c;出現服務器變成本地環回口地址&#xff0c;導致無法解析域名 C:\Users\Zsy>nslookup www.baidu.com 服務器: UnKnown Address: 127.0.0.1*** UnKnown 找不到 www.baidu.com: Server failed排查思路 嘗試關閉虛擬網卡&#…

CSS的邏輯組合偽類

CSS 的邏輯組合偽類有 4 種&#xff0c;分別是&#xff1a;:not()、:is()、:where()和&#xff1a;has()。 否定偽類:not() :not 偽類選擇器用來匹配不符合一組選擇器的元素。由于它的作用是防止特定的元素被選中&#xff0c;它也被稱為反選偽類&#xff08;negation pseudo-…