音视频封装到MP4/MP3ffmpeg(十四)
前言
音视频封装指的是将编码后的数据放入具有一定规则的容器文件中,比如MP4文件,MOV文件,MP3文件等等。容器文件和编码方法是两个不同的概念,需要区分,不过MP3即是编码方法,也是一种容器文件。音视频封装是一种很常见的应用场景,比如封装成MP4文件,便于存储和传播。MP4既可以只包含音频或者视频,也可以同时包含多个音频和视频。本文以MP4为例,将音视频数据封装到MP4容器文件中
封装相关流程
image.png
音视频的封装是基于AVFormatContext来实现的
封装相关函数
1、int avformat_alloc_output_context(AVFormatContext **outfmtCtx,AVOutputFormat *oformat,const char *format_name,const char *filename)
创建封装用的上下文,一般第二个和第三个参数设置为NULL即可,第四个参数为输出的文件路径
2、AVStream* avformat_new_stream(AVFormatContext *ouCtx,AVCodec *codec)
为封装上下文添加一路音/视频流,第二个参数一般设置为NULL;没添加一个新的流,AVFormatContext的nb_streams值加1,然后当调用av_write_frame或者av_interleave_write_frame()函数写入AVPacket数据时,将根据AVPacket中的stream_index写入到对应的AVStream中
3、int avcodec_parameters_copy(AVCodecParameters *dst,AVCodecParameters *src)
从src中拷贝编码相关参数到dst中
4、int avio_open(AVIOContext* io,const char* io_url, int flags)
初始化AVIOContext缓冲区,io_url为最终输出文件的路径,flags为AVIO_FLAG_WRITE代表创建封装用的IO缓冲区
5、int avformat_write_header(AVFormatContext *fmt,AVDictionary **options)
写入封装用的头文件信息。这一步之后AVFormatContext中的一些相关参数也会被初始化
6、int av_write_frame(AVFormatContext *fmt,AVPacket *packet)
写入数据。packet代表着压缩的音频或视频数据,它的stream_index一定要设置正确,他的pts,dts,duration也一定要是基于AVStream的time_base,因为封装文件音视频时长帧率数据是根据这三个值计算出来的。
备注:调用此方法时,dts的值一定要线性增加,不然出错
7、int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt);
和上面方法一样,区别就是它会内部维护一个缓冲区,自己根据dts的值排序后再写入,即它不需要dts的值线性增加
8、int av_write_trailer(AVFormatContext *fmt)
写入封装尾信息,一个完整的封装流程必须包含此函数的调用
实现代码
本文实现了两个例子,例子1即直接读取文件中的数据然后原封不动的再封装;例子2是将一个文件中的音频流和一个文件中的视频流合并成一个音视频文件(这里实现了MP4或MOV文件)
分别对应下面
void doReMuxer();和void doMuxerTwoFile();函数
- 例子1
#ifndef muxer_hpp
#define muxer_hpp#include <stdio.h>
#include <string>extern "C"{
#include <libavformat/avformat.h>
#include <libavutil/timestamp.h>
#include "CLog.h"
}
using namespace std;class Muxer
{
public:/** ffmpeg编译完成后支持的封装器位于libavformat目录下的muxer_list.c文件中,具体的配置在.configure文件中,如下:* print_enabled_components libavformat/muxer_list.c AVOutputFormat muxer_list $MUXER_LIST* xxxx.mp4对应的封装器为ff_mov_muxer*/Muxer();~Muxer();// 解析文件并原封不动在封装void doReMuxer();// 将两个文件中音频和视频合并,如果两个文件时间不一致,则将较长的进行截断void doMuxerTwoFile();
};公共实现代码
include "muxer.hpp"
Muxer::Muxer()
{
}
Muxer::~Muxer()
{
}
/** 遇到问题:avformat_close_input奔溃
- 分析原因:最开始是这样定义releaseResource(AVFormatContext *in_fmt1,AVFormatContext *in_fmt2,AVFormatContext *ou_fmt3)函数的;in_fmt1是一个指针变量,当外部调用此函数时
- 如果传递的实参如果是一个临时变量,那么在此函数内部就算在avformat_close_input(in_fmt1);后面执行in_fmt1=NULL;它也不能将外部传进来的实参置为NULL,如果多次调用
- releaseResource()函数并且传递的实参in_fmt1还是同一个,就会造成avformat_close_input释放多次,所以造成奔溃。
- 解决方案:将函数定义成如下方式static void releaseResource(AVFormatContext **in_fmt1,AVFormatContext **in_fmt2,AVFormatContext **ou_fmt3);事实上ffmpeg的很多释放函数也
- 是采用的指针的指针作为形参的定义方式。
*/
static void releaseResource(AVFormatContext **in_fmt1,AVFormatContext **in_fmt2,AVFormatContext **ou_fmt3)
{
if (in_fmt1 && *in_fmt1) {
avformat_close_input(in_fmt1);
*in_fmt1 = NULL;
}
if (in_fmt2 && *in_fmt2) {
avformat_close_input(in_fmt2);
*in_fmt2 = NULL;
}
if (ou_fmt3 && ou_fmt3 != NULL) {
avformat_free_context(ou_fmt3);
*ou_fmt3 = NULL;
}
}
#endif /* muxer_hpp */
例子1实现代码
void Muxer::doReMuxer()
{string curFile(__FILE__);unsigned long pos = curFile.find("1-video_encode_decode");if (pos == string::npos) {LOGD("can not find file");return;}string srcDir = curFile.substr(0,pos) + "filesources/";string srcPath = srcDir + "test_1280x720_1.mp4";string dstPath = "1-test_1280_720_1.MP4";AVFormatContext *in_fmtCtx = NULL, *ou_fmtCtx = NULL;AVStream *ou_audio_stream = NULL,*ou_video_stream = NULL;int in_audio_index = -1,in_video_index = -1;int ret = 0;if ((ret = avformat_open_input(&in_fmtCtx,srcPath.c_str(),NULL,NULL)) < 0) {LOGD("avformat_open_input fail %d",ret);return;}if (avformat_find_stream_info(in_fmtCtx ,NULL) < 0) {LOGD("avformat_find_stream_info() fail");releaseResource(&in_fmtCtx, NULL, NULL);return;}for(int i=0;i<in_fmtCtx->nb_streams;i++) {AVStream *stream = in_fmtCtx->streams[i];if (in_audio_index == -1 && stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {in_audio_index = i;}if (in_video_index == -1 && stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {in_video_index = i;}}// 创建上下文if (avformat_alloc_output_context2(&ou_fmtCtx,NULL,NULL,dstPath.c_str()) < 0){LOGD("avformat_alloc_output_context2 fail");releaseResource(&in_fmtCtx, NULL, NULL);return;}// 添加流if (in_audio_index != -1) {ou_audio_stream = avformat_new_stream(ou_fmtCtx,NULL);avcodec_parameters_copy(ou_audio_stream->codecpar,in_fmtCtx->streams[in_audio_index]->codecpar);}if (in_video_index != -1) {ou_video_stream = avformat_new_stream(ou_fmtCtx,NULL);avcodec_parameters_copy(ou_video_stream->codecpar,in_fmtCtx->streams[in_video_index]->codecpar);}// 打开AVIOContext缓冲区if (!(ou_fmtCtx->flags & AVFMT_NOFILE)) {if (avio_open(&ou_fmtCtx->pb,dstPath.c_str(),AVIO_FLAG_WRITE) < 0) {LOGD("avio_open fail");releaseResource(&in_fmtCtx, NULL, &ou_fmtCtx);return;}}// 写入头部信息if (avformat_write_header(ou_fmtCtx, NULL) < 0) {LOGD("avformat_write_header fail");return;}AVPacket *sPacket = av_packet_alloc();while (av_read_frame(in_fmtCtx, sPacket) >= 0) {LOGD("pts %d dts %d index %d",sPacket->pts,sPacket->dts,sPacket->stream_index);if (sPacket->stream_index == in_audio_index) {AVStream *stream = in_fmtCtx->streams[in_audio_index];sPacket->pts = av_rescale_q_rnd(sPacket->pts,stream->time_base,ou_audio_stream->time_base,AV_ROUND_NEAR_INF);sPacket->dts = av_rescale_q_rnd(sPacket->dts,stream->time_base,ou_audio_stream->time_base,AV_ROUND_NEAR_INF);sPacket->duration = av_rescale_q_rnd(sPacket->duration,stream->time_base,ou_audio_stream->time_base,AV_ROUND_NEAR_INF);sPacket->stream_index = ou_audio_stream->index;if (av_write_frame(ou_fmtCtx, sPacket) < 0) {LOGD("av_write_frame 1 fail ");break;}} else if (sPacket->stream_index == in_video_index) {AVStream *stream = in_fmtCtx->streams[in_video_index];sPacket->pts = av_rescale_q_rnd(sPacket->pts,stream->time_base,ou_video_stream->time_base,AV_ROUND_NEAR_INF);sPacket->dts = av_rescale_q_rnd(sPacket->dts,stream->time_base,ou_video_stream->time_base,AV_ROUND_NEAR_INF);sPacket->duration = av_rescale_q_rnd(sPacket->duration,stream->time_base,ou_video_stream->time_base,AV_ROUND_NEAR_INF);sPacket->stream_index = ou_video_stream->index;if (av_write_frame(ou_fmtCtx, sPacket) < 0) {LOGD("av_write_frame 2 fial");break;}}av_packet_unref(sPacket);}LOGD("写入完毕");// 写入尾部信息ret = av_write_trailer(ou_fmtCtx);releaseResource(&in_fmtCtx, NULL, &ou_fmtCtx);}
例子2实现代码
/** 将一个mp3音频文件和一个MP4无音频视频文件合并为一个MP4文件*/
void Muxer::doMuxerTwoFile()
{string curFile(__FILE__);unsigned long pos = curFile.find("1-video_encode_decode");if (pos == string::npos) {LOGD("not find 1-video_encode_decode");return;}string srcDic = curFile.substr(0,pos) + "filesources/";/** 遇到问题:当输入文件为mp3时,最终合成的MP4文件 在苹果系产品(mac iOS下默认播放器无声音),提示"DecoderConfigDescr::DeserializeMPEG1Or2AudioDecoderSpecificPayload: the DecoderSpecificInfo tag is incorrect"* 但是VLC及ffplay播放正常。在安卓/windows下默认播放器正常;ffmpeg命令合成的也是一样。苹果系统对MP4音频采用MP3编码的解析bug?* 备注:如果最终合成文件为MOV,则都正常*/string aduio_srcPath = srcDic + "test-mp3-1.mp3";
// string aduio_srcPath = srcDic + "test_441_f32le_2.aac";string video_srcPath = srcDic + "test_1280x720_2.mp4";string dstPath = "test.MOV";AVFormatContext *audio_fmtCtx = NULL,*video_fmtCtx = NULL;AVFormatContext *out_fmtCtx = NULL;int audio_stream_index = -1;int video_stream_index = -1;AVStream *audio_in_stream = NULL,*video_in_stream = NULL;AVStream *audio_ou_stream = NULL,*video_ou_stream = NULL;int ret = 0;// 解封装音频文件ret = avformat_open_input(&audio_fmtCtx,aduio_srcPath.c_str(),NULL,NULL);if (ret < 0) {LOGD("avformat_open_input audio fail %d",ret);return;}ret = avformat_find_stream_info(audio_fmtCtx,NULL);if (ret < 0) {LOGD("avformat_find_stream_info audio fail");releaseResource(&audio_fmtCtx, NULL, NULL);return;}for (int i= 0;i<audio_fmtCtx->nb_streams;i++) {AVStream *stream = audio_fmtCtx->streams[i];if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {audio_stream_index = i;break;}}// 解封装视频文件ret = avformat_open_input(&video_fmtCtx,video_srcPath.c_str(),NULL,NULL);if (ret < 0) {LOGD("avformat_open_input video fail %d",ret);releaseResource(&audio_fmtCtx, &video_fmtCtx, NULL);return;}/** 遇到问题:当输入音频文件是mp3时,执行avformat_find_stream_info()函数时提示"Could not find codec parameters for stream 0 (Audio: mp3, 44100 Hz, 2 channels, 128 kb/s):* unspecified frame sizeConsider increasing the value for the 'analyzeduration' and 'probesize' options";* 分析原因:库中没有编译mp3解码器导致avformat_find_stream_info()无法解码从而出现这样的错误* 解决方案:重新编译ffmpeg带上mp3解码器* 备注:avformat_find_stream_info()内部函数默认会尝试进行读取probesize的数据进行解码工作,所以对应的解码器必须要编译进来,否则此函数执行会出现上面错误*/ret = avformat_find_stream_info(video_fmtCtx,NULL);if (ret < 0) {LOGD("avformat_find_stream_info fail");releaseResource(&audio_fmtCtx, &video_fmtCtx, NULL);return;}for (int i=0;i<video_fmtCtx->nb_streams;i++) {AVStream *stream = video_fmtCtx->streams[i];if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {video_stream_index = i;break;}}// 创建封装上下文,用于封装到MP4文件中ret = avformat_alloc_output_context2(&out_fmtCtx,NULL,NULL,dstPath.c_str());if (ret < 0) {LOGD("avformat_alloc_output_context2 fail %d",ret);releaseResource(&audio_fmtCtx, &video_fmtCtx, NULL);return;}// 添加用于输出的流,并赋值编码相关参数if (audio_stream_index != -1) {audio_ou_stream = avformat_new_stream(out_fmtCtx,NULL);audio_in_stream = audio_fmtCtx->streams[audio_stream_index];ret = avcodec_parameters_copy(audio_ou_stream->codecpar,audio_in_stream->codecpar);}if (video_stream_index != -1) {video_ou_stream = avformat_new_stream(out_fmtCtx,NULL);video_in_stream = video_fmtCtx->streams[video_stream_index];if (avcodec_parameters_copy(video_ou_stream->codecpar,video_in_stream->codecpar) < 0) {LOGD("avcodec_parameters_copy null");releaseResource(&audio_fmtCtx, &video_fmtCtx, &out_fmtCtx);return;}}// 创建AVFormatContext的AVIOContext(参数采用系统默认),用于封装时缓冲区if (!(out_fmtCtx->flags & AVFMT_NOFILE)) {if (avio_open(&out_fmtCtx->pb,dstPath.c_str(),AVIO_FLAG_WRITE) < 0) {LOGD("avio_open fail");return;}}// 写入头文件if ((ret = avformat_write_header(out_fmtCtx,NULL)) < 0){LOGD("avformat_write_header %d",ret);releaseResource(&audio_fmtCtx, &video_fmtCtx, &out_fmtCtx);return;}AVPacket *aSPacket = av_packet_alloc();AVPacket *vSPacket = av_packet_alloc();bool audio_finish = false,video_finish = false;bool found_audio = false,found_video = false;int64_t org_pts = 0;do {if (!audio_finish && !found_audio && audio_stream_index != -1) {if (av_read_frame(audio_fmtCtx,aSPacket)< 0) {audio_finish = true;}// 不是所需的音频数据if (!audio_finish && /**found_audio && */aSPacket->stream_index != audio_stream_index) {av_packet_unref(aSPacket);continue;}if (!audio_finish) {found_audio = true;// 改变packet的pts,dts,durationaSPacket->stream_index = audio_ou_stream->index;aSPacket->pts = av_rescale_q_rnd(org_pts,audio_in_stream->time_base,audio_ou_stream->time_base,AV_ROUND_UP);aSPacket->dts = av_rescale_q_rnd(org_pts,audio_in_stream->time_base,audio_ou_stream->time_base,AV_ROUND_UP);org_pts += aSPacket->duration;aSPacket->duration = av_rescale_q_rnd(aSPacket->duration,audio_in_stream->time_base,audio_ou_stream->time_base,AV_ROUND_UP);aSPacket->pos = -1;// 要写入的packet的stream_index必须要设置正确aSPacket->stream_index = audio_ou_stream->index;AVRational tb = audio_ou_stream->time_base;LOGD("audio pts:%s dts:%s duration %s size %d finish %d",av_ts2timestr(aSPacket->pts,&tb),av_ts2timestr(aSPacket->dts,&tb),av_ts2timestr(aSPacket->duration,&tb),aSPacket->size,audio_finish);}}if (!video_finish && !found_video && video_stream_index != -1) {if (av_read_frame(video_fmtCtx,vSPacket) < 0) {video_finish = true;}/** 遇到问题:写入视频数据时提示"[mp4 @ 0x10100ae00] Application provided invalid, non monotonically increasing dts to muxer in stream 1: 8000 >= 0"错误* 分析原因:调用av_write_frame()写入视频或者音频时 dts必须是依次增长的(pts 可以不用),这里是因为这里有个逻辑错误,将视频文件中的音频数据误当做视频* 来写入了,导致了dts的错误;如下,多加了一个found_video变量*/if (!video_finish && /**found_video && */vSPacket->stream_index != video_stream_index) {av_packet_unref(vSPacket);continue;}if (!video_finish) {found_video = true;/** 遇到问题:写入视频的帧率信息* 分析原因:avformat_parameters_copy()只是将编码参数进行了赋值,再进行封装时,帧率,视频时长是根据AVPacket的pts,dts,duration进行* 计算的,所以这个时间就一定要和AVstream的time_base对应。* 解决方案:进行如下的时间基的转换*/// 要写入的packet的stream_index必须要设置正确vSPacket->stream_index = video_ou_stream->index;vSPacket->pts = av_rescale_q_rnd(vSPacket->pts,video_in_stream->time_base,video_ou_stream->time_base,AV_ROUND_INF);vSPacket->dts = av_rescale_q_rnd(vSPacket->dts, video_in_stream->time_base, video_ou_stream->time_base, AV_ROUND_INF);vSPacket->duration = av_rescale_q_rnd(vSPacket->duration, video_in_stream->time_base, video_ou_stream->time_base, AV_ROUND_INF);AVRational tb = video_in_stream->time_base;LOGD("video pts:%s dts:%s duration %s size %d key:%d finish %d",av_ts2timestr(vSPacket->pts,&tb),av_ts2timestr(vSPacket->dts,&tb),av_ts2timestr(vSPacket->duration,&tb),vSPacket->size,vSPacket->flags&AV_PKT_FLAG_KEY,video_finish);}}// 打印日志
// if (found_audio && !audio_finish) {
// AVRational tb = audio_in_stream->time_base;
// LOGD("audio pts:%s dts:%s duration %s size %d finish %d",
// av_ts2timestr(aSPacket->pts,&tb),
// av_ts2timestr(aSPacket->dts,&tb),
// av_ts2timestr(aSPacket->duration,&tb),
// aSPacket->size,audio_finish);
// }
// if (found_video && !video_finish) {
// AVRational tb = video_in_stream->time_base;
// LOGD("video pts:%s dts:%s duration %s size %d key:%d finish %d",
// av_ts2timestr(vSPacket->pts,&tb),
// av_ts2timestr(vSPacket->dts,&tb),
// av_ts2timestr(vSPacket->duration,&tb),
// vSPacket->size,vSPacket->flags&AV_PKT_FLAG_KEY,video_finish);
// }/** 遇到问题:[mp4 @ 0x10200b400] Timestamps are unset in a packet for stream 0. This is deprecated and will stop working in the future.* Fix your code to set the timestamps properly* 分析原因:从mp3读取的packet,其pts和dts均为0*/// 写入数据 视频在前if (found_video && found_audio && !video_finish && !audio_finish) {if ((ret = av_compare_ts(aSPacket->pts,audio_ou_stream->time_base,vSPacket->pts,video_ou_stream->time_base)) > 0) {// 写入视频if ((ret = av_write_frame(out_fmtCtx,vSPacket)) < 0) {LOGD("av_write_frame video 1 fail %d",ret);releaseResource(&audio_fmtCtx, &video_fmtCtx, &out_fmtCtx);break;}LOGD("写入了视频 1");found_video = false;av_packet_unref(vSPacket);} else {// 写入音频if ((ret = av_write_frame(out_fmtCtx,aSPacket)) < 0) {LOGD("av_write_frame audio 1 fail %d",ret);releaseResource(&audio_fmtCtx, &video_fmtCtx, &out_fmtCtx);break;}LOGD("写入了音频 1");found_audio = false;av_packet_unref(aSPacket);}} else if (!video_finish && found_video && !found_audio){// 写入视频if ((ret = av_write_frame(out_fmtCtx,vSPacket)) < 0) {LOGD("av_write_frame video 2 fail %d",ret);releaseResource(&audio_fmtCtx, &video_fmtCtx, &out_fmtCtx);break;}LOGD("写入了视频 2");found_video = false;av_packet_unref(vSPacket);} else if (!found_video && found_audio && !audio_finish) {// 写入音频if ((ret = av_write_frame(out_fmtCtx,aSPacket)) < 0) {LOGD("av_write_frame audio 2 fail %d",ret);break;}LOGD("写入了音频 2");found_audio = false;av_packet_unref(aSPacket);}} while(!audio_finish && !video_finish);// 结束写入av_write_trailer(out_fmtCtx);// 释放内存releaseResource(&audio_fmtCtx, &video_fmtCtx, &out_fmtCtx);
}
遇到问题
1、avformat_close_input()奔溃
分析原因:最开始是这样定义releaseResource(AVFormatContext *in_fmt1,AVFormatContext *in_fmt2,AVFormatContext *ou_fmt3)函数的;in_fmt1是一个指针变量,当外部调用此函数时如果传递的实参如果是一个临时变量,那么在此函数内部就算在avformat_close_input(in_fmt1);后面执行in_fmt1=NULL;它也不能将外部传进来的实参置为NULL,如果多次调用
releaseResource()函数并且传递的实参in_fmt1还是同一个,就会造成avformat_close_input释放多次,所以造成奔溃。
解决方案:将函数定义成如下方式static void releaseResource(AVFormatContext **in_fmt1,AVFormatContext **in_fmt2,AVFormatContext **ou_fmt3);事实上ffmpeg的很多释放函数也是采用的指针的指针作为形参的定义方式。
2、当输入音频文件是mp3时,执行avformat_find_stream_info()函数时提示"Could not find codec parameters for stream 0 (Audio: mp3, 44100 Hz, 2 channels, 128 kb/s):unspecified frame sizeConsider increasing the value for the 'analyzeduration' and 'probesize' options";
分析原因:库中没有编译mp3解码器导致avformat_find_stream_info()无法解码从而出现这样的错误
解决方案:重新编译ffmpeg带上mp3解码器
3、写入视频数据时提示"[mp4 @ 0x10100ae00] Application provided invalid, non monotonically increasing dts to muxer in stream 1: 8000 >= 0"错误
分析原因:调用av_write_frame()写入视频或者音频时 dts必须是依次增长的(pts可以不用),这里是因为这里有个逻辑错误,将视频文件中的音频数据误当做视频来写入了,导致了dts的错误;如下,多加了一个found_video变量
4、写入视频的帧率信息不正确
分析原因:avformat_parameters_copy()只是将编码参数进行了赋值,再进行封装时,帧率,视频时长是根据AVPacket的pts,dts,duration进行计算的,所以这个时间就一定要和AVstream的time_base对应。
解决方案:调用av_write_frame()对pts,dts,duration进行时间基的转换
5、[mp4 @ 0x10200b400] Timestamps are unset in a packet for stream 0. This is deprecated and will stop working in the future.Fix your code to set the timestamps properly
分析原因:从mp3读取的packet,其pts和dts均为0
解决方案:给packet添加pts和dts值,具体参考代码
6、当输入文件为mp3时,最终合成的MP4文件 在苹果系产品(maciOS下默认播放器无声音),提示"DecoderConfigDescr::DeserializeMPEG1Or2AudioDecoderSpecificPayload: the DecoderSpecificInfo tag is incorrect"但是VLC及ffplay播放正常。在安卓/windows下默认播放器正常;ffmpeg命令合成的也是一样。
备注:如果最终合成文件为MOV,则都正常
解决方案:苹果系统对MP4音频采用MP3编码的解析bug?暂时还不太清楚原因
项目代码
示例地址
示例代码位于cppsrc目录下文件
muxer.hpp
muxer.cpp
项目下示例可运行于iOS/android/mac平台,工程分别位于demo-ios/demo-android/demo-mac三个目录下,可根据需要选择不同平台
音视频封装到MP4/MP3ffmpeg(十四)相关推荐
- 音视频封装:MP4结构概述和分析工具
音视频封装:MP4结构概述和分析工具 Original 潇湘落木 智媒黑板报 2019-10-09 18:15 问题背景: 前面已经讲了好几种封装格式包括了TS.FLV.RTP等.现在用几篇文章讲解下 ...
- mp4分离h265_音视频封装:MP4结构概述和分析工具
问题背景: 前面已经讲了好几种封装格式包括了TS.FLV.RTP等.现在用几篇文章讲解下MP4,这种封装格式设计思路和前面都不太一样,其应用范围最广.灵活性最高.跨平台最好,兼容性最强.带来的负面影响 ...
- AVI音视频封装格式学习(四)——linux系统C语言AVI格式音视频封装应用
拖了很久的AVI音视频封装实例,花了一天时间终于调完了,兼容性不是太好,但作为参考学习使用应该没有问题.RIFF和AVI以及WAV格式,可以参考前面的一些文章.这里详细介绍将一个H264视频流和一个2 ...
- 重学音视频?认识 MP4 视频(下)
接上一篇文章: 重学音视频?认识 MP4 视频(上) 文章的提到的资料都放在知识星球了,后续的内容更新还是以星球为主,也会放出一些干货在公众号的,现在加入星球还是优惠价,后面干货越多,涨价的可能性就越 ...
- AVI音视频封装格式学习(三)——AVI 数据结构解析
这里介绍AVI会使用到的数据结构,为了避免翻译引入歧义,决定该部分还是使用英文原文,如后续有时间再进行翻译. AVIMAINHEADER structure The AVIMAINHEADER str ...
- AVI音视频封装格式学习(五)——h265与PCM合成AVI文件
不知道是处于版权收费问题还是什么原因,H265现在也并没有非常广泛的被普及.将h265数据合成AVI的资料现在在网上也基本上没有.使用格式化工厂工具将h265数据封装成AVI格式,发现它在封装的时候其 ...
- 音视频封装格式、编码格式
音视频封装格式.编码格式 概述 常见的AVI.RMVB.MKV.ASF.WMV.MP4.3GP.FLV等文件其实只能算是一种封装标准. 一个完整的视频文件是由音频和视频2部分组成的.H264.Xvid ...
- AVI音视频封装格式学习(二)——AVI RIFF文件参考
AVI RIFF文件参考 AVI RIFF File Reference 微软AVI文件格式是与捕获,编辑和播放音视频流的应用程序一起使用的RIFF文件规范.通常,AVI文件包含多个不同类型的数据流. ...
- 常见的音视频封装和编码
常见的的音视频封装和编码格式是怎么样的? 封装格式的推出机构和领域? 封装格式数据结构是什么样的? 视频播放器原理 播放器在显示上,可以分为DirectShow框架的工具,比如VLC,mplayer: ...
最新文章
- 这就是为什么我们需要在React的类组件中绑定事件处理程序
- 『高级篇』docker之DockerSwarm的集群环境搭建(28)
- A wizard’s guide to Adversarial Autoencoders: Part 2, Exploring latent space with Adversarial Autoen
- matlab 二值化_撸了一份 ostu二值化,需要的小伙伴请拿走
- python自学笔记之开源小工具:SanicDB介绍
- redis sentinel 主从切换(failover)解决方案,详细配置
- 使用纯粹的ABAP位操作实现两个整数相加
- 依附B2B平台照样做搜索营销
- Uvaoj 10048 - Audiophobia(Floyd算法变形)
- linux下安装nginx的采坑记录
- 【转】UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc7 in position 1: invalid continuation 汉字编码
- AD14简单使用教程
- python excel 颜色填充 excel样式
- 利用redis生成订单号
- react native Animated 使用详解(基础)
- 搜索引擎下拉食云速捷详细_移动端下拉框寻云速捷/-/移动端下拉框跃云速捷-...
- 外贸软件进出口内贸综合型管理解决方案
- C++-STL-组件(一)-容器05:stack(栈)
- java.lang.String cannot be cast to com.rock.bpo.agent.base.LoginUser
- es相同条件搜索多次返回结果不一样
热门文章
- Phalanx HDU 【2859】
- Android底部导航栏的三种风格实现
- apple pay充游戏后退款_2019,7月苹果王者荣耀退款
- ClassFactory 无法供应请求的类 (异常来自 HRESULT:0x80040111 (CLASS_E_CLASSNOTAVAILABLE))
- 详解Unity中的粒子系统Particle System (九)
- docker 导致宿主机重启的解决方法
- iOS小技能:合并mp3格式的文件
- JDK下载过慢的问题解决方案
- Windows运行程序时桌面窗口卡死
- C++ 编译宏的一些符号