最简单的基于 FFmpeg 的视音频分离器 - 简化版

最简单的基于 FFmpeg 的视音频分离器 - 简化版

  • 最简单的基于 FFmpeg 的视音频分离器 - 简化版
    • 正文
    • 结果
    • 工程文件下载
    • 参考链接

最简单的基于 FFmpeg 的视音频分离器 - 简化版

参考雷霄骅博士的文章,链接:最简单的基于FFmpeg的封装格式处理:视音频分离器简化版(demuxer-simple)

正文

本文介绍一个视音频分离器(Demuxer)。

视音频分离器即是将封装格式数据(例如 MKV)中的视频压缩数据(例如 H.264)和音频压缩数据(例如 AAC)分离开,如下图所示。

在这里插入图片描述

在这个过程中并不涉及到编码和解码。

本文记录的程序将一个 FLV 封装的文件(其中视频编码为 H.264,音频编码为 MP3)分离成为两个文件:一个 H.264 编码的视频码流文件,一个 MP3 编码的音频码流文件。

需要注意的是,本文介绍的是一个简单版的视音频分离器(Demuxer)。该分离器的优点是代码十分简单,很好理解。但是缺点是并不适用于一些格式。对于 MP3 编码的音频是没有问题的。但是在分离 MP4/FLV/MKV 等一些格式中的 AAC 编码的码流的时候,得到的 AAC 码流是不能播放的。原因是存储 AAC 数据的 AVPacket 的 data 字段中的数据是不包含 7 字节 ADTS 文件头的“砍头”的数据,是无法直接解码播放的(当然如果在这些数据前面手工加上 7 字节的 ADTS 文件头的话,就可以播放了)。

分离某些封装格式(例如 MP4/FLV/MKV 等)中的 H.264 的时候,需要首先写入 SPS 和 PPS,否则会导致分离出来的数据没有 SPS、PPS 而无法播放。H.264 码流的 SPS 和 PPS 信息存储在 AVCodecContext 结构体的 extradata 中。需要使用 FFmpeg 中名称为“h264_mp4toannexb”的 bitstream filter 处理。有两种处理方式:

(1)使用 bitstream filter 处理每个 AVPacket(简单)

把每个 AVPacket 中的数据(data 字段)经过 bitstream filter “过滤”一遍。关键函数是 av_bitstream_filter_filter()。示例代码如下:


	AVBitStreamFilterContext* h264bsfc =  av_bitstream_filter_init("h264_mp4toannexb"); 
	while(av_read_frame(ifmt_ctx, &pkt)>=0){
		if(pkt.stream_index==videoindex){
			av_bitstream_filter_filter(h264bsfc, ifmt_ctx->streams[videoindex]->codec, NULL, &pkt.data, &pkt.size, pkt.data, pkt.size, 0);
			fwrite(pkt.data,1,pkt.size,fp_video);
			//...
		}
		av_free_packet(&pkt);
	}
	av_bitstream_filter_close(h264bsfc);

上述代码中,把 av_bitstream_filter_filter() 的输入数据和输出数据(分别对应第 4,5,6,7 个参数)都设置成 AVPacket 的 data 字段就可以了。

需要注意的是 bitstream filter 需要初始化和销毁,分别通过函数 av_bitstream_filter_init() 和 av_bitstream_filter_close()。

经过上述代码处理之后,AVPacket 中的数据有如下变化:

  • 每个 AVPacket 的data 添加了 H.264 的 NALU 的起始码 {0,0,0,1}。

  • 每个 IDR 帧数据前面添加了 SPS 和 PPS。

(2)手工添加 SPS,PPS(稍微复杂)

将 AVCodecContext 的 extradata 数据经过 bitstream filter 处理之后得到 SPS、PPS,拷贝至每个 IDR 帧之前。下面代码示例了写入 SPS、PPS 的过程。

FILE *fp=fopen("test.264","ab");
AVCodecContext *pCodecCtx=...  
unsigned char *dummy=NULL;   
int dummy_len;  
AVBitStreamFilterContext* bsfc =  av_bitstream_filter_init("h264_mp4toannexb");    
av_bitstream_filter_filter(bsfc, pCodecCtx, NULL, &dummy, &dummy_len, NULL, 0, 0);  
fwrite(pCodecCtx->extradata,pCodecCtx->extradata_size,1,fp);  
av_bitstream_filter_close(bsfc);    
free(dummy);

然后修改 AVPacket 的 data。把前 4 个字节改为起始码。示例代码如下所示:

char nal_start[]={0,0,0,1};
memcpy(packet->data,nal_start,4);

经过上述两步也可以得到可以播放的 H.264 码流,相对于第一种方法来说复杂一些。

当封装格式为 MPEG2TS 的时候,不存在上述问题。

程序的流程如下图所示:

在这里插入图片描述

从流程图中可以看出,将每个通过 av_read_frame() 获得的 AVPacket 中的数据直接写入文件即可。

简单介绍一下流程中各个重要函数的意义:

  1. avformat_open_input():打开输入文件。
  2. av_read_frame():获取一个 AVPacket。
  3. fwrite():根据得到的 AVPacket 的类型不同,分别写入到不同的文件中。

源程序:

// Simplest FFmpeg Demuxer Simple.cpp : 定义控制台应用程序的入口点。
//


/**
* 最简单的基于 FFmpeg 的视音频分离器(简化版)
* Simplest FFmpeg Demuxer Simple
*
* 源程序:
* 雷霄骅 Lei Xiaohua
* leixiaohua1020@126.com
* 中国传媒大学/数字电视技术
* Communication University of China / Digital TV Technology
* http://blog.csdn.net/leixiaohua1020
*
* 修改:
* 刘文晨 Liu Wenchen
* 812288728@qq.com
* 电子科技大学/电子信息
* University of Electronic Science and Technology of China / Electronic and Information Science
* https://blog.csdn.net/ProgramNovice
*
* 本程序可以将封装格式中的视频码流数据和音频码流数据分离出来。
* 在该例子中, 将FLV的文件分离得到 H.264 视频码流文件和 MP3 音频码流文件。
*
* 注意:
* 这个是简化版的视音频分离器。
* 与原版的不同在于,没有初始化输出视频流和音频流的 AVFormatContext,
* 而是直接将解码后的得到的 AVPacket 中的的数据通过 fwrite() 写入文件。
* 这样做的好处是流程比较简单。
* 坏处是对一些格式的视音频码流是不适用的,比如说 FLV/MP4/MKV 等格式中的 AAC 码流
* (上述封装格式中的 AAC 的 AVPacket 中的数据缺失了 7 字节的 ADTS 文件头)。
*
* This software split a media file (in Container such as MKV, FLV, AVI...)
* to video and audio bitstream.
* In this example, it demux a FLV file to H.264 bitstream and MP3 bitstream.
* 
* Note:
* This is a simple version of "Simplest FFmpeg Demuxer". 
* It is more simple because it doesn't init Output Video/Audio stream's AVFormatContext.
* It writes AVPacket's data to files directly.
* The advantages of this method is simple.
* The disadvantages of this method is it's not suitable for some kind of bitstreams.
* Forexample, AAC bitstream in FLV/MP4/MKV Container Format
* (data in AVPacket lack of 7 bytes of ADTS header).
*
*/

#include "stdafx.h"

#include <stdio.h>
#include <stdlib.h>

// 解决报错:'fopen': This function or variable may be unsafe.Consider using fopen_s instead.
#pragma warning(disable:4996)

// 解决报错:无法解析的外部符号 __imp__fprintf,该符号在函数 _ShowError 中被引用
#pragma comment(lib, "legacy_stdio_definitions.lib")
extern "C"
{
	// 解决报错:无法解析的外部符号 __imp____iob_func,该符号在函数 _ShowError 中被引用
	FILE __iob_func[3] = { *stdin, *stdout, *stderr };
}

#define __STDC_CONSTANT_MACROS

#ifdef _WIN32
// Windows
extern "C"
{
#include "libavformat/avformat.h"
};
#else
// Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavformat/avformat.h>
#ifdef __cplusplus
};
#endif
#endif


// 1: Use H.264 Bitstream Filter 
#define USE_H264BSF 1

int main(int argc, char* argv[])
{
	AVFormatContext *ifmt_ctx = NULL;
	AVPacket pkt;

	int ret;
	int videoindex = -1, audioindex = -1;

	// Input file URL
	const char *in_filename = "cuc_ieschool.flv";
	// Output video file URL
	const char *out_video_filename = "cuc_ieschool.h264";
	// Output audio file URL
	const char *out_audio_filename = "cuc_ieschool.mp3";

	av_register_all();

	// 输入
	ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0);
	if (ret < 0)
	{
		printf("Could not open input file.\n");
		return -1;
	}

	ret = avformat_find_stream_info(ifmt_ctx, 0);
	if (ret < 0)
	{
		printf("Failed to retrieve input stream information.\n");
		return -1;
	}

	// Print some input information
	av_dump_format(ifmt_ctx, 0, in_filename, 0);

	for (size_t i = 0; i < ifmt_ctx->nb_streams; i++)
	{
		if (ifmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
			videoindex = i;
		else if (ifmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
			audioindex = i;
	}

	FILE *fp_audio = fopen(out_audio_filename, "wb+");
	FILE *fp_video = fopen(out_video_filename, "wb+");

	/*
	FIX: H.264 in some container formats (FLV, MP4, MKV etc.)
	need "h264_mp4toannexb" bitstream filter (BSF).
	1. Add SPS,PPS in front of IDR frame
	2. Add start code ("0,0,0,1") in front of NALU
	H.264 in some containers (such as MPEG2TS) doesn't need this BSF.
	*/
#if USE_H264BSF
	AVBitStreamFilterContext* h264bsfc = av_bitstream_filter_init("h264_mp4toannexb");
#endif

	while (1)
	{
		// 获取一个 AVPacket
		ret = av_read_frame(ifmt_ctx, &pkt);
		if (ret < 0)
		{
			break;
		}

		if (pkt.stream_index == videoindex)
		{
#if USE_H264BSF
			av_bitstream_filter_filter(h264bsfc, ifmt_ctx->streams[videoindex]->codec, NULL, &pkt.data, &pkt.size, pkt.data, pkt.size, 0);
#endif
			printf("Write a video packet. size:%d\tpts:%lld\n", pkt.size, pkt.pts);
			fwrite(pkt.data, 1, pkt.size, fp_video);
		}
		else if (pkt.stream_index == audioindex)
		{
			/*
			AAC in some container formats (FLV, MP4, MKV etc.) need to
			add 7 Bytes ADTS Header in front of AVPacket data manually.
			Other Audio Codec (MP3...) works well.
			*/
			printf("Write a audio packet. size:%d\tpts:%lld\n", pkt.size, pkt.pts);
			fwrite(pkt.data, 1, pkt.size, fp_audio);
		}
		av_free_packet(&pkt);
	}


#if USE_H264BSF
	av_bitstream_filter_close(h264bsfc);
#endif

	fclose(fp_video);
	fclose(fp_audio);

	avformat_close_input(&ifmt_ctx);

	system("pause");
	return 0;
}

结果

运行程序,输出如下:

在这里插入图片描述

输入文件为:
cuc_ieschool.flv:FLV 封装格式数据。

在这里插入图片描述

输出文件为:

cuc_ieschool.mp3:MP3 音频码流数据。

在这里插入图片描述

cuc_ieschool.h264:H.264 视频码流数据。

在这里插入图片描述

在这里插入图片描述

工程文件下载

GitHub:UestcXiye / Simplest-FFmpeg-Demuxer-Simple

CSDN:Simplest FFmpeg Demuxer Simple.zip

参考链接

  1. 使用FFMPEG类库分离出多媒体文件中的音频码流
  2. 使用FFMPEG类库分离出多媒体文件中的H.264码流

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/406116.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

开源免费的NTFS for mac工具mounty

开源免费的NTFS for mac工具mounty 安装依赖 brew install gromgit/fuse/ntfs-3g-macbrew install --cask macfuse安装mounty 如果已经安装macFUSE和ntfs-3g-mac&#xff0c;可以直接点击下载的dmg安装包&#xff0c;安装升级。第一次启动mounty&#xff0c;你需要接受一系列…

DFT系列文章之 《DFT Scan chain》

我们知道,运用DFT技术的基本步骤: 1. 规划scan chain 规划 scan chain,首先将电路中的普通 DFF 换成 scan DFF: 2. scan cell 替换 scan DFF 是在原DFF 的输入端增加了一个 MUX,于是多了几个 pin :scan_in,scan_enable,scan_out。换完之后将所有的 scan DFF 首尾依…

dpdk环境搭建和工作原理

文章目录 1、DPDK环境搭建1.1、环境搭建1.2、编译DPDK 2、DPDK工作原理 1、DPDK环境搭建 1.1、环境搭建 工具准备&#xff1a;VMware、ubuntu16.04。 &#xff08;1&#xff09;VMware添加两个网卡。桥接网卡作为 DPDK 运行的网卡&#xff0c;NAT 网卡作为 ssh 连接的网卡。 …

C++奇怪的 ::template

答疑解惑 怎么会有::template的写法 起初 在阅读stl的源码的时候&#xff0c;发现了一条诡异的代码 // ALIAS TEMPLATE _Rebind_alloc_t template<class _Alloc,class _Value_type> using _Rebind_alloc_t typename allocator_traits<_Alloc>::template rebind…

AMRT3D数字孪生引擎详解

AMRT 3D数字孪生引擎介绍 AMRT3D引擎是一款融合了眸瑞科技的AMRT格式与轻量化处理技术为基础&#xff0c;以降本增效为目标&#xff0c;支持多端发布的一站式纯国产自研的CS架构项目开发引擎。 引擎包括场景搭建、UI拼搭、零代码交互事件、光影特效组件、GIS/BIM组件、实时数据…

洛谷B2084 质因数分解 题解

#题外话&#xff08;第36篇题解&#xff09;&#xff08;本题为普及-难度&#xff09;&#xff08;c语言&#xff09; #先看题目 #思路 从2遍历到n-1&#xff0c;如果被遍历的数是n的因数&#xff0c;且它是质数&#xff0c;且 n/遍历数 也是个质数&#xff0c;那么n/遍历数就…

LLMs之Gemma:Gemma(Google开发的新一代领先的开源模型)的简介、安装、使用方法之详细攻略

LLMs之Gemma&#xff1a;Gemma(Google开发的新一代领先的开源模型)的简介、安装、使用方法之详细攻略 导读&#xff1a;此文章介绍了Google推出的新一代开源模型Gemma&#xff0c;旨在帮助研发人员负责任地开发AI。 背景&#xff1a; >> Google长期致力于为开发者和研究人…

Java向ES库中插入数据报错:I/O reactor status: STOPPED

Java向ES库中插入数据报错&#xff1a;java.lang.IllegalStateException: Request cannot be executed; I/O reactor status: STO 一、问题问题原因 二、解决思路 一、问题 在使用Java向ES库中插入数据时&#xff0c;第一次成功插入&#xff0c;第二次出现以下错误&#xff1a…

Python reversed函数

在Python编程中&#xff0c;reversed()函数是一个内置函数&#xff0c;用于反转序列对象的元素顺序。这个函数可以应用于列表、元组、字符串等可迭代对象&#xff0c;并返回一个反向迭代器&#xff0c;可以按照相反的顺序遍历序列中的元素。本文将深入探讨Python中的reversed()…

springboot邮箱注册

1.准备工作 操作之前准备两个邮箱 我准备了网易邮箱和QQ邮箱&#xff0c;网易邮箱用来发送验证码&#xff0c;QQ邮箱用来做注册&#xff08;希望大家和我一样&#xff0c;不然可能会出错 &#xff09; 发送验证码的邮箱需要开启一些设置&#xff0c;否则不…

FFmpeg解析之avformat_find_stream_info函数

avformat_find_stream_info 的主要作用就是&#xff1a;解析媒体文件并获取相关的流信息 整体的逻辑如下图所示&#xff1a; /*** Read packets of a media file to get stream information. This* is useful for file formats with no headers such as MPEG. This* function…

【Java程序设计】【C00276】基于Springboot的就业信息管理系统(有论文)

基于Springboot的就业信息管理系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的就业信息管理系统 本系统分为前台功能模块、管理员功能模块、学生功能模块、企业功能模块以及导师功能模块。 前台功能模块&…

第十四章[面向对象]:14.8:枚举类

一,定义枚举类 1,把一个类定义为枚举类: 只需要让它继承自 enum 模块中的 Enum 类即可。 例如在下面的例子中,Weekday 类继承自 Enum 类, 则表明这是一个枚举类 枚举类的每个成员都由 2 部分组成,分别是 name 和 value, 其中 name 属性值为该枚举值的变量名(如下例中: …

SwiftUI 支持拖放功能的集合视图(Grid)如何捕获手指按下并抬起这一操作

功能需求 假设我们开发了一款 SwiftUI 应用,其中用户可以通过拖放 Grid 中的 Cell 来完成一些操作。现在,我们希望用户在某个 Cell 被按下并随后抬起手指时得到通知,这能够实现吗? 如上图所示,我们准确地捕获到了手指在 Grid 的 Cell 上按下再抬起这一操作!那么它是如何…

电子元件分销商

Top 10 Active Electronic Parts Distributors List – 2022 / 2023 一家从众多制造商那里收购所有电子元件并销售给客户的公司被称为电子元件分销商。 A company that acquires all electronic components from numerous manufacturers and sells them to customers from a si…

如何做bug分析 ?bug分析什么 ? 为什么要做bug分析 ?

每当我们完成一个版本测试时&#xff0c;总会在测试报告中添加一些分析bug的指标 &#xff0c;主要用于分析在测试过程中存在的问题 。但是在分析的过程中你就可能遇到如下的问题 &#xff1a; 我应该分析那些指标呢 &#xff1f;每一个具体的指标该如何分析 &#xff1f;它能说…

使用Python制作进度条有多少种方法?看这一篇文章就够了!

前言 偶然间刷到一个视频&#xff0c;说到&#xff1a;当程序正在运算时&#xff0c;会有一个较长时间的空白期&#xff0c;谁也不知道程序运行的进度如何&#xff0c;不如给他加个进度条。 于是我今个就搜寻一下&#xff0c;Python版的进度条都可以怎么写&#xff01; 正文…

【Logback】Logback 日志框架的架构

目录 1、Logger&#xff08;记录器&#xff09; &#xff08;1&#xff09;有效级别和级别继承 &#xff08;2&#xff09;日志打印和日志筛选 &#xff08;3&#xff09;记录器命名 2、Appenders&#xff08;追加器&#xff09; 3、Layouts&#xff08;布局&#xff09;…

【深度学习】LoRA: Low-Rank Adaptation of Large Language Models,论文解读

文章&#xff1a; https://arxiv.org/abs/2106.09685 文章目录 摘要介绍LoRA的特点什么是低秩适应矩阵&#xff1f;什么是适应阶段&#xff1f;低秩适应矩阵被注入到预训练模型的每一层Transformer结构中&#xff0c;这一步是如何做到的&#xff1f; 摘要 自然语言处理的一个重…

Docusaurus框架——快速搭建markdown文档站点介绍sora

文章目录 ⭐前言⭐初始化项目&#x1f496; 创建项目&#xff08;react-js&#xff09;&#x1f496; 运行项目&#x1f496; 目录文件&#x1f496; 创建一个jsx页面&#x1f496; 创建一个md文档&#x1f496; 创建一个介绍sora的文档 ⭐总结⭐结束 ⭐前言 大家好&#xff0…