音视频大合集第三篇;深入探索
Posted 初一十五啊
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了音视频大合集第三篇;深入探索相关的知识,希望对你有一定的参考价值。
前言
关于音视频篇是那么有点长,关于音视频初识篇和初探篇已经完了,接下来是深入探索篇,还有一部分面试内容。
10.19-24音视频中高级52部+面试
10.25-26高级Android组件化强化实战(一二)
10.27-11.3高级Android组件化强化实战(大厂架构演化20章)
中间所有的周六周日都休息😁
关注公众号:初一十五a
解锁 《Android十二大板块文档》
音视频大合集,从初中高到面试应有尽有;让学习更贴近未来实战。已形成PDF版
十二个模块内容如下:
1.2022最新Android11位大厂面试专题,128道附答案
2.音视频大合集,从初中高到面试应有尽有
3.Android车载应用大合集,从零开始一起学
4.性能优化大合集,告别优化烦恼
5.Framework大合集,从里到外分析的明明白白
6.Flutter大合集,进阶Flutter高级工程师
7.compose大合集,拥抱新技术
8.Jetpack大合集,全家桶一次吃个够
9.架构大合集,轻松应对工作需求
10.Android基础篇大合集,根基稳固高楼平地起
11.Flutter番外篇:Flutter面试+项目实战+电子书
12.大厂高级Android组件化强化实战
整理不易,关注一下吧。开始进入正题,ღ( ´・ᴗ・` ) 🤔
一丶FFmpeg命令行工具:查看媒体文件头信息工具ffprobe
1.简述
ffprobe是ffmpeg命令行工具中相对简单的,此命令是用来查看媒体文件格式的工具。
2.命令格式
在命令行中输入如下格式的命令:
ffprobe [文件名]
3.使用ffprobe查看mp3格式的文件
本文使用的是歌曲《社会摇》,执行的命令为:
ffprobe shy.mp3
输出内容为:
Input #0, mp3, from 'shy.mp3':
Metadata:
genre : Blues
encoder : Lavf56.4.101
comment : 163 key(Don't modify):L64FU3W4YxX3ZFTmbZ+8/UO6KmVXLfTij3uZN/wCXE4a00XHtvOwccwFlS+8ednRD4MnrdUH+aUYZFVY8bObsrabtBM2Ps/UAWPJtsmW/3RXnn6eJcNUHrPALM0003fIpQnn6MOWbdXqog6WFDLpaZJhoPMnFy9u41HxCalUwMEc+mkHNn+nSLlioJfpv4wPBwUhxfLNmOScmXPzOary2k37A/brRx7QUlMD9rkaZ
album : 社会摇
title : 社会摇
artist : 萧全
track : 1
Duration: 00:04:09.34, start: 0.025056, bitrate: 323 kb/s
Stream #0:0: Audio: mp3, 44100 Hz, stereo, s16p, 320 kb/s
Stream #0:1: Video: mjpeg, yuvj444p(pc, bt470bg/unknown/unknown), 500x500 [SAR 72:72 DAR 1:1], 90k tbr, 90k tbn, 90k tbc
Metadata:
comment : Media (e.g. label side of CD)
首先我们看以下这行信息:
Duration: 00:04:09.34, start: 0.025056, bitrate: 323 kb/s
这行信息表示,该视频文件的时长是4分9秒340毫秒,开始播放时间是0.025056,整个文件的比特率是256Kbit/s,然后我们看下一行信息:
Stream ####0:0: Audio: mp3, 44100 Hz, stereo, s16p, 320 kb/s
这行信息表示,第一个流是音频流,编码格式是MP3格式,采样率是44.1KHz,声道是立体声,采样表示格式是SInt16(short)的planner(平铺格式),这路流的比特率320Kbit/s。
4.使用ffprobe查看mp4格式的文件
本文使用的是视频《泡沫》,执行的命令为:
ffprobe pm.mp4
输出内容为:
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'pm.mp4':
Metadata:
major_brand : isom
minor_version : 1
compatible_brands: isomavc1
creation_time : 2016-12-17T16:02:05.000000Z
album : Yinyuetai
artist : yinyuetai.com
comment : Yinyuetai-1TR1151
date : 12/18/16 00:02:05
Duration: 00:04:33.51, start: 0.000000, bitrate: 1104 kb/s
Stream #0:0(und): Video: h264 (Main) (avc1 / 0x31637661), yuv420p, 960x540, 1008 kb/s, 25 fps, 25 tbr, 25k tbn, 50 tbc (default)
Metadata:
creation_time : 2016-12-17T16:02:05.000000Z
handler_name : 264@GPAC0.5.1-DEV-rev5472
Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 92 kb/s (default)
Metadata:
creation_time : 2016-12-17T15:50:54.000000Z
handler_name : Sound Media Handler
首先我们看以下这行信息:
Duration: 00:04:33.51, start: 0.000000, bitrate: 1104 kb/s
这行信息表示,该视频文件的时长是4分33秒510毫秒,开始播放时间是0,整个文件的比特率是1104Kbit/s,然后我们看下一行信息:
Stream ####0:0(und): Video: h264 (Main) (avc1 / 0x31637661), yuv420p, 960x540, 1008 kb/s, 25 fps, 25 tbr, 25k tbn, 50 tbc (default)
这行信息表示,第一个流是视频流,编码格式是H264格式(封装格式为AVC1),每一帧的数据表示为yuv420p,分辨率为960x540,这路流的比特率为1108Kbit/s,帧率为每秒钟25帧。
接下来我们看下一行:
Stream ####0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 92 kb/s (default)
这行信息表示第二个流是音频流,编码方式为ACC(封装格式为MP4A),并且采用的Profile是LC规格,采样率是44.1KHz,声道是立体声,这路流的比特率92Kbit/s。
到此为止,我们就掌握了使用ffprobe提取媒体的头文件信息的方式,并了解了提取出来的信息的含义
5.ffprobe高级使用方式
1. 输出格式信息
appledeMacBook-Pro:Desktop renhui$ ffprobe -show_format pm.mp4
ffprobe version 3.4.2 Copyright (c) 2007-2018 the FFmpeg developers
built with Apple LLVM version 9.0.0 (clang-900.0.39.2)
configuration: --prefix=/usr/local/Cellar/ffmpeg/3.4.2 --enable-shared --enable-pthreads --enable-version3 --enable-hardcoded-tables --enable-avresample --cc=clang --host-cflags= --host-ldflags= --disable-jack --enable-gpl --enable-libmp3lame --enable-libx264 --enable-libxvid --enable-opencl --enable-videotoolbox --disable-lzma
libavutil 55. 78.100 / 55. 78.100
libavcodec 57.107.100 / 57.107.100
libavformat 57. 83.100 / 57. 83.100
libavdevice 57. 10.100 / 57. 10.100
libavfilter 6.107.100 / 6.107.100
libavresample 3. 7. 0 / 3. 7. 0
libswscale 4. 8.100 / 4. 8.100
libswresample 2. 9.100 / 2. 9.100
libpostproc 54. 7.100 / 54. 7.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'pm.mp4':
Metadata:
major_brand : isom
minor_version : 1
compatible_brands: isomavc1
creation_time : 2016-12-17T16:02:05.000000Z
album : Yinyuetai
artist : yinyuetai.com
comment : Yinyuetai-1TR1151
date : 12/18/16 00:02:05
Duration: 00:04:33.51, start: 0.000000, bitrate: 1104 kb/s
Stream #0:0(und): Video: h264 (Main) (avc1 / 0x31637661), yuv420p, 960x540, 1008 kb/s, 25 fps, 25 tbr, 25k tbn, 50 tbc (default)
Metadata:
creation_time : 2016-12-17T16:02:05.000000Z
handler_name : 264@GPAC0.5.1-DEV-rev5472
Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 92 kb/s (default)
Metadata:
creation_time : 2016-12-17T15:50:54.000000Z
handler_name : Sound Media Handler
[FORMAT]
filename=pm.mp4
nb_streams=2
nb_programs=0
format_name=mov,mp4,m4a,3gp,3g2,mj2
format_long_name=QuickTime / MOV
start_time=0.000000
duration=273.506667
size=37776599
bit_rate=1104955
probe_score=100
TAG:major_brand=isom
TAG:minor_version=1
TAG:compatible_brands=isomavc1
TAG:creation_time=2016-12-17T16:02:05.000000Z
TAG:album=Yinyuetai
TAG:artist=yinyuetai.com
TAG:comment=Yinyuetai-1TR1151
TAG:date=12/18/16 00:02:05
[/FORMAT]
2. 输出每个流的具体信息(以JSON格式)
appledeMacBook-Pro:Desktop renhui$ ffprobe -print_format json -show_streams pm.mp4
ffprobe version 3.4.2 Copyright (c) 2007-2018 the FFmpeg developers
built with Apple LLVM version 9.0.0 (clang-900.0.39.2)
configuration: --prefix=/usr/local/Cellar/ffmpeg/3.4.2 --enable-shared --enable-pthreads --enable-version3 --enable-hardcoded-tables --enable-avresample --cc=clang --host-cflags= --host-ldflags= --disable-jack --enable-gpl --enable-libmp3lame --enable-libx264 --enable-libxvid --enable-opencl --enable-videotoolbox --disable-lzma
libavutil 55. 78.100 / 55. 78.100
libavcodec 57.107.100 / 57.107.100
libavformat 57. 83.100 / 57. 83.100
libavdevice 57. 10.100 / 57. 10.100
libavfilter 6.107.100 / 6.107.100
libavresample 3. 7. 0 / 3. 7. 0
libswscale 4. 8.100 / 4. 8.100
libswresample 2. 9.100 / 2. 9.100
libpostproc 54. 7.100 / 54. 7.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'pm.mp4':
Metadata:
major_brand : isom
minor_version : 1
compatible_brands: isomavc1
creation_time : 2016-12-17T16:02:05.000000Z
album : Yinyuetai
artist : yinyuetai.com
comment : Yinyuetai-1TR1151
date : 12/18/16 00:02:05
Duration: 00:04:33.51, start: 0.000000, bitrate: 1104 kb/s
Stream #0:0(und): Video: h264 (Main) (avc1 / 0x31637661), yuv420p, 960x540, 1008 kb/s, 25 fps, 25 tbr, 25k tbn, 50 tbc (default)
Metadata:
creation_time : 2016-12-17T16:02:05.000000Z
handler_name : 264@GPAC0.5.1-DEV-rev5472
Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 92 kb/s (default)
Metadata:
creation_time : 2016-12-17T15:50:54.000000Z
handler_name : Sound Media Handler
"streams": [
"index": 0,
"codec_name": "h264",
"codec_long_name": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10",
"profile": "Main",
"codec_type": "video",
"codec_time_base": "1/50",
"codec_tag_string": "avc1",
"codec_tag": "0x31637661",
"width": 960,
"height": 540,
"coded_width": 960,
"coded_height": 540,
"has_b_frames": 2,
"sample_aspect_ratio": "0:1",
"display_aspect_ratio": "0:1",
"pix_fmt": "yuv420p",
"level": 31,
"chroma_location": "left",
"refs": 1,
"is_avc": "true",
"nal_length_size": "4",
"r_frame_rate": "25/1",
"avg_frame_rate": "25/1",
"time_base": "1/25000",
"start_pts": 0,
"start_time": "0.000000",
"duration_ts": 6835000,
"duration": "273.400000",
"bit_rate": "1008649",
"bits_per_raw_sample": "8",
"nb_frames": "6835",
"disposition":
"default": 1,
"dub": 0,
"original": 0,
"comment": 0,
"lyrics": 0,
"karaoke": 0,
"forced": 0,
"hearing_impaired": 0,
"visual_impaired": 0,
"clean_effects": 0,
"attached_pic": 0,
"timed_thumbnails": 0
,
"tags":
"creation_time": "2016-12-17T16:02:05.000000Z",
"language": "und",
"handler_name": "264@GPAC0.5.1-DEV-rev5472"
,
"index": 1,
"codec_name": "aac",
"codec_long_name": "AAC (Advanced Audio Coding)",
"profile": "LC",
"codec_type": "audio",
"codec_time_base": "1/44100",
"codec_tag_string": "mp4a",
"codec_tag": "0x6134706d",
"sample_fmt": "fltp",
"sample_rate": "44100",
"channels": 2,
"channel_layout": "stereo",
"bits_per_sample": 0,
"r_frame_rate": "0/0",
"avg_frame_rate": "0/0",
"time_base": "1/44100",
"start_pts": 0,
"start_time": "0.000000",
"duration_ts": 12061696,
"duration": "273.507846",
"bit_rate": "92649",
"max_bit_rate": "136240",
"nb_frames": "11779",
"disposition":
"default": 1,
"dub": 0,
"original": 0,
"comment": 0,
"lyrics": 0,
"karaoke": 0,
"forced": 0,
"hearing_impaired": 0,
"visual_impaired": 0,
"clean_effects": 0,
"attached_pic": 0,
"timed_thumbnails": 0
,
"tags":
"creation_time": "2016-12-17T15:50:54.000000Z",
"language": "und",
"handler_name": "Sound Media Handler"
]
3. 显示帧信息
ffprobe -show_frames pm.mp4
4. 查看包信息
ffprobe -show_packets pm.mp4
二丶FFmpeg命令行工具:播放媒体文件的工具ffplay
1.简述
ffplay是以FFmpeg框架为基础,外加渲染音视频的库libSDL构建的媒体文件播放器。
在使用ffplay之前必须要安装到系统中,
2.命令格式
在安装了在命令行中输入如下格式的命令:
ffplay [选项] ['输入文件']
主要选项
'-x width' 强制以 "width" 宽度显示
'-y height' 强制以 "height" 高度显示
'-an' 禁止音频
'-vn' 禁止视频
'-ss pos' 跳转到指定的位置(秒)
'-t duration' 播放 "duration" 秒音/视频
'-bytes' 按字节跳转
'-nodisp' 禁止图像显示(只输出音频)
'-f fmt' 强制使用 "fmt" 格式
'-window_title title' 设置窗口标题(默认为输入文件名)
'-loop number' 循环播放 "number" 次(0将一直循环)
'-showmode mode' 设置显示模式
可选的 mode :
'0, video' 显示视频
'1, waves' 显示音频波形
'2, rdft' 显示音频频带
默认值为 'video',你可以在播放进行时,按 "w" 键在这几种模式间切换
'-i input_file' 指定输入文件
一些高级选项
'-sync type' 设置主时钟为音频、视频、或者外部。默认为音频。主时钟用来进行音视频同步
'-threads count' 设置线程个数
'-autoexit' 播放完成后自动退出
'-exitonkeydown' 任意键按下时退出
'-exitonmousedown' 任意鼠标按键按下时退出
'-acodec codec_name' 强制指定音频解码器为 "codec_name"
'-vcodec codec_name' 强制指定视频解码器为 "codec_name"
'-scodec codec_name' 强制指定字幕解码器为 "codec_name"
一些快捷键
'q, ESC' 退出
'f' 全屏
'p, SPC' 暂停
'w' 切换显示模式(视频/音频波形/音频频带)
's' 步进到下一帧
'left/right' 快退/快进 10 秒
'down/up' 快退/快进 1 分钟
'page down/page up' 跳转到前一章/下一章(如果没有章节,快退/快进 10 分钟)
'mouse click' 跳转到鼠标点击的位置(根据鼠标在显示窗口点击的位置计算百分比)
3.ffplay 播放音频
播放音频文件的命令:
ffplay shy.mp3
这时候就会弹出来一个窗口,一边播放MP3文件,一边将播放音频的图画到该窗口上。针对该窗口的操作如下:
- 点击该窗口的任意一个位置,ffplay会按照点击的位置计算出时间的进度,然后seek到计算出来的时间点继续播放。
- 按下键盘的左键默认快退10s,右键默认快进10s,上键默认快进1min,下键默认快退1min。
- 按ESC就退出播放进程,按W会绘制音频的波形图。 相关效果图片如下:
4.ffplay 播放视频
播放视频文件的命令:
ffplay pm.mp4
这时候,就会在新弹出的窗口上播放该视频了。
- 如果想要同时播放多个文件,只需在多个命令行下同时执行ffplay就可以了。
- 如果按s键就可以进入frame-step模式,即按s键一次就会播放下一帧图像。
5.ffplay 高级使用方式
循环播放
ffplay pm.mp4 -loop 10
上述命令代表播放视频结束之后会从头再次播放,共循环播放10次。
播放 pm.mp4 ,播放完成后自动退出
ffplay -autoexit pm.mp4
以 320 x 240 的大小播放 test.mp4
ffplay -x 320 -y 240 pm.mp4
将窗口标题设置为 “myplayer”,循环播放 2 次
ffplay -window_title myplayer -loop 2 pm.mp4
播放 双通道 32K 的 PCM 音频数据
ffplay -f s16le -ar 32000 -ac 2 test.pcm
6.ffplay音画同步
ffplay也是一个视频播放器,所以不得不提出来的一个问题是:音画同步。ffplay的音画同步的实现方式其实有三种,分别是:以音频为主时间轴作为同步源,以视频为主时间轴作为同步源,以外部时钟为主时间轴作为同步源。
下面就以音频为主时间轴来作为同步源来作为案例进行讲解,而且ffplay默认也是以音频为基准进行对齐的,那么以音频作为对齐基准是如何实现的呢?
首先需要说明的是,播放器接收到的视频帧或者音频帧,内部都是会有时间戳(PTS时钟)来标识它实际应该在什么时刻展示,实际的对齐策略如下:比较视频当前的播放时间和音频当前的播放时间,如果视频播放过快,则通过加大延迟或者重复播放来降低视频播放速度,如果视频播放满了,则通过减小延迟或者丢帧来追赶音频播放的时间点。关键就在于音视频时间的比较和延迟的计算,当前在比较的过程中会设置一个阈值,如果超过预设的阈值就应该作出调整(丢帧或者重复渲染),这就是整个对齐策略。
在使用ffplay的时候,我们可以明确的指定使用那种对齐方式,比如:
ffplay pm.mp4 -sync audio
上面这个命令显式的指定了使用以音频为基准进行音视频同步的方式播放视频文件,当然这也是ffplay的默认播放设置。
ffplay pm.mp4 -sync video
上面这个命令显式的指定了使用以视频为基准进行音视频同步的方式播放视频文件。
ffplay pm.mp4 -sync ext
上面这个命令显式的指定了使用外部时钟为基准进行音视频同步的方式播放视频文件。
大家可以分别使用这三种方式进行播放,尝试听一听,做一些快进或者seek的操作,看看不同的对齐策略对最终的播放会产生什么样的影响。
三丶FFmpeg命令行工具:媒体文件转换工具ffmpeg
1.简述
ffmpeg是一个非常强大的工具,它可以转换任何格式的媒体文件,并且还可以用自己的AudioFilter以及VideoFilter进行处理和编辑。有了它,我们就可以对媒体文件做很多我们想做的事情了。
2.命令行参数
通用参数
- -f fmt : 指定格式
- -i filename:指定输入文件名
- -y:覆盖已有文件
- -t duration:指定时长
- -fs limit_size:设置文件大小的上限
- -ss time_off: 从指定的时间开始
- -re:代表按照时间戳读取或发送数据,尤其在作为推流工具的时候一定要加上该参数,否则ffpmeg会按照最高速率向流媒体不停的发送数据。
- -map:指定输出文件的流映射关系。例如:“-map 1:0 -map 1:1”要求按照第二个输入的文件的第一个流和第二个流写入输出文件。如果没有设置此项,则ffpmeg采用默认的映射关系。
视频参数
- -b:指定比特率(bit/s),ffmpeg默认采用的是VBR的,若指定的该参数,则使用平均比特率。
- -bitexact:使用标准比特率。
- -vb:指定视频比特率(bit/s)
- -r rate:帧速率(fps)
- -s size:指定分辨率(320x240)
- -aspect aspect:设置视频长宽比(4:3、16:9或1.33333、1.77777)
- -croptop size:设置顶部切除尺寸(in pixels)
- -cropleft size:设置左切除尺寸(in pixels)
- -cropbottom size:设置地步切除尺寸(in pixels)
- -cropright size:设置右切除尺寸(in pixels)
- -padtop size:设置顶部补齐尺寸(in pixels)
- -padleft size:设置左补齐尺寸(in pixels)
- -padbottom size:设置地步补齐尺寸(in pixels)
- -padright size:设置右补齐尺寸(in pixels)
- -padcolor color:设置补齐颜色
- -vn:取消视频的输出
- -vcodec codec:强制使用codec编码方式
音频参数
- -ab:设置比特率(bit/s),对于MP3的格式,想要听到较高品质的声音,建议设置160Kbit/s(单声道80Kbit/s)以上。
- -aq quality:设置音频质量
- -ar ratre:设置音频采样率(Hz)
- -ac channels:设置声道数,1就是单声道,2就是立体声
- -an:取消音频输出
- -acodec codec:强制使用codec编码方式
- -vol volume:设置录制音量大小
以上就是在日常开发中经常用到的音视频参数及通用参数。下面会针对常见的开发场景进行实践和说明。
3.实践学习
3.1.列出ffmpeg支持的所有格式
相关命令:
ffmpeg -formats
输出结果:
File formats:
D. = Demuxing supported
.E = Muxing supported
--
D 3dostr 3DO STR
E 3g2 3GP2 (3GPP2 file format)
E 3gp 3GP (3GPP file format)
D 4xm 4X Technologies
E a64 a64 - video for Commodore 64
D aa Audible AA format files
D aac raw ADTS AAC (Advanced Audio Coding)
DE ac3 raw AC-3 省略......
D xbin eXtended BINary text (XBIN)
D xmv Microsoft XMV
D xpm_pipe piped xpm sequence
D xvag Sony PS3 XVAG
D xwma Microsoft xWMA
D yop Psygnosis YOP
DE yuv4mpegpipe YUV4MPEG pipe
3.2.剪切一段媒体文件,可以是音频或者视频文件
相关命令:
ffmpeg -i pm.mp4 -ss 00:00:50.0 -codec copy -t 20 output.mp4
命令说明:
表示将文件pm.mp4从第50s开始剪切20s的时间,输出到output.mp4中,其中-ss指定偏移时间(time Offset),-t指定的时长(duration)。
但是直接这样执行命令,固然我们能截取出来音视频的文件,但是当我们播放的时候,我们会发现虽然ffmepg剪切视频,很方便,但是也有很大缺陷:
(1). 剪切时间点不精确 (2). 有时剪切的视频开头有黑屏
造成这些问题的原因是ffmpeg无法seek到非关键帧上。
命令层面定位的话就是如果把-ss, -t参数放在-i参数之后,是对输出文件执行的seek操作 输入文件会逐帧解码,直到-ss设置的时间点为止,这么操作会很慢,虽然时间点是准确的,但是很容易出现黑屏问题。
所以:我们优化了一下上面的那个命令,让视频的剪切更加精确:
ffmpeg -ss 10 -t 15 -accurate_seek -i pm.mp4 -codec copy output.mp4
注意:accurate_seek必须放在-i参数之前。
但是,可能又会有人发现,还是存在剪切不准确的现象,那是因为,上述命令只是进行了数据的转封装,会受到关键帧的影响,所以如果需要特别准确的剪切,只能使用ffmpeg进行重新编解码的操作了,命令行如下:
ffmpeg -i input.mp4 -ss 00:00:03.123 -t 10 -c:v libx264 -c:a aac out.mp4
此命令行相对上面的转封装的剪切来说,速度明显变慢,是因为对视频数据重新编解码了,但是精度相对转封装来说是大大提高了。
3.3.提取视频文件中的音频数据,并保存为文件
相关命令:
ffmpeg -i pm.mp4 -vn -acodec copy output.m4a
命令说明:
将文件pm.mp4的视频流禁用掉(参数为:-vn,如果禁用音频流参数为-an,禁用字母流参数为-sn )。
然后将pm.mp4中的音频流的数据封装到output.m4a文件中,音频流的编码格式不变。
3.4.将视频中的音频静音,只保留视频
相关命令:
ffmpeg -i pm.mp4 -an -vcodec copy output.mp4
命令说明:
将文件pm.mp4的音频流禁用掉(参数为:-an )。
然后将pm.mp4中的视频流的数据封装到output.mp4文件中,视频流的编码格式不变。
3.4.从mp4文件中抽取视频流导出为裸H264数据:
相关命令:
ffmpeg -i pm.mp4 -an -vcodec copy -bsf:v h264_mp4toannexb output.h264
命令说明:
在指令中,我们舍弃了音频数据(-an),视频数据使用mp4toannexb这个bitstreasm filter来转换为原始的H264数据。(注:同一编码也会有不同的封装格式)。
验证播放:
可以使用ffplay命令进行尝试播放,如果能播放成功,则说明生效。
3.5.将视频推送到流媒体服务器上:
ffmpeg -re -i pm.mp4 -acodec copy -vcodec copy -f flv rtmp://127.0.0.1/rh/mylive
命令说明:
将mp4文件的音视频数据的编码格式不变,按照rtmp的方式,将视频推送到流媒体服务器上。
3.6.将流媒体服务器上的流dump到本地:
ffmpeg -i rtmp://127.0.0.1/rh/mylive -acodec copy -vcodec copy -f flv test.flv
命令说明:
将流媒体服务器的数据,不进行转码,通过转封装的方式保存到本地。
3.7.给视频添加水印
ffmpeg -i pm.mp4 -i xxx.png -filter_complex "overlay=5:5" out.mp4
命令说明:
使用ffmpeg滤镜功能,将对mp4添加水印。
3.8.倒放音视频
// 1.视频倒放,无音频
ffmpeg.exe -i inputfile.mp4 -filter_complex [0:v]reverse[v] -map [v] -preset superfast reversed.mp4
// 2.视频倒放,音频不变
ffmpeg.exe -i inputfile.mp4 -vf reverse reversed.mp4
// 3.音频倒放,视频不变
ffmpeg.exe -i inputfile.mp4 -map 0 -c:v copy -af "areverse" reversed_audio.mp4
// 4.音视频同时倒放
ffmpeg.exe -i inputfile.mp4 -vf reverse -af areverse -preset superfast reversed.mp4
3.9.将几个MP4视频文件合并为1个视频.
实现思路:
1.先将MP4文件转化为同样编码形式的ts流(ts流支持concate)
2.第二步,连接(concate)ts流
3.最后,把连接好的ts流转化为MP4.
// 转换为ts流ffmpeg -i 0.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 0.ts
ffmpeg -i 1.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 1.ts
ffmpeg -i 2.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 2.ts
ffmpeg -i 3.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 3.ts
ffmpeg -i 4.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 4.ts
ffmpeg -i 5.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 5.ts
// 合并ts流为mp4
ffmpeg -i "concat:0.ts|1.ts|2.ts|3.ts|4.ts|5.ts" -acodec copy -vcodec copy -absf aac_adtstoasc FileName.mp4
四丶FFmpeg命令行工具:FFmpeg 调整音视频播放速度
FFmpeg对音频、视频播放速度的调整的原理不一样。下面简单的说一下各自的原理及实现方式:
1.调整视频速率
调整视频速率的原理为:修改视频的pts,dts
实现:
ffmpeg -i input.mkv -an -filter:v "setpts=0.5*PTS" output.mkv
注意:视频调整的速度倍率范围为:[0.25, 4]
如果只调整视频的话最好把音频禁掉。
对视频进行加速时,如果不想丢帧,可以用-r 参数指定输出视频FPS,方法如下:
ffmpeg -i input.mkv -an -r 60 -filter:v "setpts=2.0*PTS" output.mkv
2.调整音频速率
调整视频速率的原理为:简单的方法是调整音频采样率,但是这种方法会改变音色, 一般采用通过对原音进行重采样,差值等方法。
ffmpeg -i input.mkv -filter:a "atempo=2.0" -vn output.mkv
注意:倍率调整范围为[0.5, 2.0]
如果需要调整4倍可采用以下方法:
ffmpeg -i input.mkv -filter:a "atempo=2.0,atempo=2.0" -vn output.mkv
如果需要同时调整,可以采用如下的方式来实现:* *
ffmpeg -i input.mkv -filter_complex "[0:v]setpts=0.5*PTS[v];[0:a]atempo=2.0[a]" -map "[v]" -map "[a]" output.mkv
五丶FFmpeg 简介
1.FFmpeg 介绍
FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库。
2.FFmpeg 组成
- libavformat:用于各种音视频封装格式的生成和解析,包括获取解码所需信息以生成解码上下文结构和读取音视频帧等功能;
- libavcodec:用于各种类型声音/图像编解码;
- libavutil:包含一些公共的工具函数;
- libswscale:用于视频场景比例缩放、色彩映射转换;
- libpostproc:用于后期效果处理;
- ffmpeg:该项目提供的一个工具,可用于格式转换、解码或电视卡即时编码等;
- ffsever:一个 HTTP 多媒体即时广播串流服务器;
- ffplay:是一个简单的播放器,使用ffmpeg 库解析和解码,通过SDL显示;
3.FFmpeg包含类库说明
类库说明
- libavformat - 用于各种音视频封装格式的生成和解析,包括获取解码所需信息、读取音视频数据等功能。各种流媒体协议代码(如rtmpproto.c等)以及音视频格式的(解)复用代码(如flvdec.c、flvenc.c等)都位于该目录下。
- libavcodec - 音视频各种格式的编解码。各种格式的编解码代码(如aacenc.c、aacdec.c等)都位于该目录下。
- libavutil - 包含一些公共的工具函数的使用库,包括算数运算,字符操作等。
- libswscale - 提供原始视频的比例缩放、色彩映射转换、图像颜色空间或格式转换的功能。
- libswresample - 提供音频重采样,采样格式转换和混合等功能。
- libavfilter - 各种音视频滤波器。
- libpostproc - 用于后期效果处理,如图像的去块效应等。
- libavdevice - 用于硬件的音视频采集、加速和显示。
如果您之前没有阅读FFmpeg代码的经验,建议优先阅读libavformat、libavcodec以及libavutil下面的代码,它们提供了音视频开发的最基本功能,应用范围也是最广的。
常用结构
FFmpeg里面最常用的数据结构,按功能可大致分为以下几类(以下代码行数,以branch: origin/release/3.4为准):
封装格式
- AVFormatContext - 描述了媒体文件的构成及基本信息,是统领全局的基本结构体,贯穿程序始终,很多函数都要用它作为参数;
- AVInputFormat - 解复用器对象,每种作为输入的封装格式(例如FLV、MP4、TS等)对应一个该结构体,如libavformat/flvdec.c的ff_flv_demuxer;
- AVOutputFormat - 复用器对象,每种作为输出的封装格式(例如FLV, MP4、TS等)对应一个该结构体,如libavformat/flvenc.c的ff_flv_muxer;
- AVStream - 用于描述一个视频/音频流的相关数据信息。
编解码
- AVCodecContext - 描述编解码器上下文的数据结构,包含了众多编解码器需要的参数信息;
- AVCodec - 编解码器对象,每种编解码格式(例如H.264、AAC等)对应一个该结构体,如libavcodec/aacdec.c的ff_aac_decoder。每个AVCodecContext中含有一个AVCodec;
- AVCodecParameters - 编解码参数,每个AVStream中都含有一个AVCodecParameters,用来存放当前流的编解码参数。
网络协议
- AVIOContext - 管理输入输出数据的结构体;
- URLProtocol - 描述了音视频数据传输所使用的协议,每种传输协议(例如HTTP、RTMP)等,都会对应一个URLProtocol结构,如libavformat/http.c中的ff_http_protocol;
- URLContext - 封装了协议对象及协议操作对象。
数据存放
- AVPacket - 存放编码后、解码前的压缩数据,即ES数据;
- AVFrame - 存放编码前、解码后的原始数据,如YUV格式的视频数据或PCM格式的音频数据等;
六丶Mac下安装FFmpeg
1.安装ffmpeg
分为两种安装方式:
2.命令行安装
brew install ffmpeg
3.下载压缩包安装
去 下载7z压缩包,解压缩后,将ffmpeg文件拷贝到一个地方,然后在bash_profile里面配置好环境变量
4.安装ffplay
分为两种安装方式:
4.1.命令行安装
执行下面的命令就可以进行安装操作
brew install ffmpeg --with-ffplay
- 注:目前使用此安装方式安装后,执行ffplay会出现command not found的问题,可能是因为SDL的配置问题导致的。
4.2.下载压缩包安装
去下载7z压缩包,解压缩后,将ffplay文件拷贝到一个地方,然后在bash_profile里面配置好环境变量
5.附言
在上面我们接触到了命令行安装ffmpeg的方法,除了安装选项 --with-ffplay外还有更多的选项如下:
–with-fdk-aac (Enable the Fraunhofer FDK AAC library)
–with-ffplay (Enable FFplay media player)
–with-freetype (Build with freetype support)
–with-frei0r (Build with frei0r support)
–with-libass (Enable ASS/SSA subtitle format)
–with-libcaca (Build with libcaca support)
–with-libvo-aacenc (Enable VisualOn AAC encoder)
–with-libvorbis (Build with libvorbis support)
–with-libvpx (Build with libvpx support)
–with-opencore-amr (Build with opencore-amr support)
–with-openjpeg (Enable JPEG 2000 image format)
–with-openssl (Enable SSL support)
–with-opus (Build with opus support)
–with-rtmpdump (Enable RTMP protocol)
–with-schroedinger (Enable Dirac video format)
–with-speex (Build with speex support)
–with-theora (Build with theora support)
–with-tools (Enable additional FFmpeg tools)
–without-faac (Build without faac support)
–without-lame (Disable MP3 encoder)
–without-x264 (Disable H.264 encoder)
–without-xvid (Disable Xvid MPEG-4 video encoder)
–devel (install development version 2.1.1)
–HEAD (install HEAD version)
七丶将 FFmpeg 移植到 android平台
首先需要去FFmpeg的官网去下载FFmpeg的源码
下载的文件为压缩包,解压后得到ffmpeg目录。
修改ffmpeg的configure文件:
# 原来的配置内容:
SLIBNAME_WITH_MAJOR='$(SLIBNAME).$(LIBMAJOR)'
LIB_INSTALL_EXTRA_CMD='$$(RANLIB)"$(LIBDIR)/$(LIBNAME)"'
SLIB_INSTALL_NAME='$(SLIBNAME_WITH_VERSION)'
SLIB_INSTALL_LINKS='$(SLIBNAME_WITH_MAJOR)$(SLIBNAME)'
#替换后的内容:
SLIBNAME_WITH_MAJOR='$(SLIBPREF)$(FULLNAME)-$(LIBMAJOR)$(SLIBSUF)'
LIB_INSTALL_EXTRA_CMD='$$(RANLIB)"$(LIBDIR)/$(LIBNAME)"'
SLIB_INSTALL_NAME='$(SLIBNAME_WITH_MAJOR)'
SLIB_INSTALL_LINKS='$(SLIBNAME)'
原因:如果不修改配置,直接进行编译出来的so文件类似libavcodec.so.55.39.101,文件的版本号位于so之后,这样在Android上无法加载,所以需要修改!
编写build_android.sh脚本文件:
在编译FFmpeg之前需要进行配置,设置相应的环境变量等。所有的配置选项都在ffmpeg-3.3/configure这个脚本文件中,执行如下命令可查看所有的配置选项:
$ ./configure –help
下面将配置项和环境变量设置写成一个sh脚本文件来运行以便编译出Android平台需要的so文件出来。
build_android.sh的内容如下:
#!/bin/bash
NDK=/Users/renhui/framework/android-ndk-r14b
SYSROOT=$NDK/platforms/android-9/arch-arm/
TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64
function build_one
./configure \\
--prefix=$PREFIX \\
--enable-shared \\
--disable-static \\
--disable-doc \\--enable-cross-compile \\
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \\
--target-os=linux \\
--arch=arm \\
--sysroot=$SYSROOT \\
--extra-cflags="-Os -fpic $ADDI_CFLAGS" \\
--extra-ldflags="$ADDI_LDFLAGS" \\
$ADDITIONAL_CONFIGURE_FLAG
CPU=arm
PREFIX=$(pwd)/android/$CPU
ADDI_CFLAGS="-marm"
build_one
需要确定的是NDK,SYSROOT和TOOLCHAIN是否是本地的环境,并确定cross-prefix指向的路径存在。
保存脚本文件后,将脚本的权限提升:
chmod 777 build_android.sh
然后执行脚本,该脚本会完成对ffmpeg的配置,并生成config.h等配置文件,后面的编译会用到。如果未经过配置直接进行编译会提示无法找到config.h文件等错误。
然后执行下面两个命令:
$make
$make install
至此,会在ffmpeg目录下生成一个android目录,其/android/arm/lib目录下的so库文件就是能够在Android上运行的so库。
创建Demo工程,测试上面生成的so文件能否正常使用:
-
创建一个新的Android工程
-
在工程根目录下创建jni文件夹
-
在jni下创建prebuilt目录,然后:将上面编译成功的so文件放入到该目录下
-
创建包含native方法的类,先在src下创建cn.renhui包,然后创建FFmpegNative.java类文件。主要包括加载so库文件和一个native测试方法两部分,其内容如下:
package cn.renhui; public class FFmpegNative static System.loadLibrary("avutil-55"); System.loadLibrary("avcodec-57"); System.loadLibrary("swresample-2"); System.loadLibrary("avformat-57"); System.loadLibrary("swscale-4"); System.loadLibrary("avfilter-6"); System.loadLibrary("avdevice-57"); System.loadLibrary("ffmpeg_codec"); public native int avcodec_find_decoder(int codecID);
-
用javah创建.头文件: classes目录,执行:javah-jni cn.renhui.FFmpegNative,会在当前目录产生cn_renhui_FFmpegNative.h的C头文件;
-
根据头文件名,建立相同名字c文件cn_renhui_FFmpegNative.c,在这个源文件中实现头文件中定义的方法,代码如下:
#include "cn_renhui_FFmpegNative.h" #ifdef __cplusplus extern "C" #endif JNIEXPORT jint JNICALL Java_cn_renhui_FFmpegNative_avcodec_1find_1decoder (JNIEnv *env, jobject obj, jint codecID) AVCodec *codec = NULL; /* register all formats and codecs */ av_register_all(); codec = avcodec_find_decoder(codecID); if (codec != NULL) return 0; else return -1; #ifdef __cplusplus #endif
-
编写Android.mk,内容如下:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := avcodec-57-prebuilt LOCAL_SRC_FILES := prebuilt/libavcodec-57.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := avdevice-57-prebuilt LOCAL_SRC_FILES := prebuilt/libavdevice-57.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := avfilter-6-prebuilt LOCAL_SRC_FILES := prebuilt/libavfilter-6.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := avformat-57-prebuilt LOCAL_SRC_FILES := prebuilt/libavformat-57.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := avutil-55-prebuilt LOCAL_SRC_FILES := prebuilt/libavutil-55.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := avswresample-2-prebuilt LOCAL_SRC_FILES := prebuilt/libswresample-2.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := swscale-4-prebuilt LOCAL_SRC_FILES := prebuilt/libswscale-4.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := ffmpeg_codec LOCAL_SRC_FILES := cn_dennishucd_FFmpegNative.c LOCAL_LDLIBS := -llog -ljnigraphics -lz -landroid LOCAL_SHARED_LIBRARIES := avcodec-57-prebuilt avdevice-57-prebuilt avfilter-6-prebuilt avformat-57-prebuilt avutil-55-prebuilt include $(BUILD_SHARED_LIBRARY)
-
编译so文件,执行ndk-build
-
新建一个Activity,进行测试,测试核心代码:
FFmpegNative ffmpeg = new FFmpegNative(); int codecID = 28; int res = ffmpeg.avcodec_find_decoder(codecID); if (res == 0) tv.setText("Success!"); else tv.setText("Failed!");
28是H264的编解码ID,可以在ffmpeg的源代码中找到,它是枚举类型定义的。在C语言中,可以换算为整型值。这里测试能否找到H264编解码,如果能找到,说明调用ffmpeg的库函数是成功的,这也表明我们编译的so文件是基本可用。
八丶FFmpeg API 介绍与通用 API 分析
1.FFmpeg 编解码流程
FFmpeg编解码流程图如下,此图包含了整体的解封装、编解码的基本流程。
下面我们要介绍的术语及相关API都是围绕这个流程图展开的。
2.FFmpeg 相关术语
1. 容器/文件(Container/File):即特定格式的多媒体文件,比如MP4,flv,mov等。
2. 媒体流(Stream):表示在时间轴上的一段连续的数据,比如一段声音数据、一段视频数据或者一段字母数据,可以是压缩的,也可以是非压缩的,压缩的数据需要关联特定的编解码器。
3. 数据帧/数据包(Frame/Packet):通常一个媒体流是由大量的数据帧组成的,对于压缩数据,帧对应着编解码器的最小处理单元,分属于不同媒体流的数据帧交错存储与容器之中。
4. 编解码器:编解码器是以帧为单位实现压缩数据和原始数据之间的相互转换的。
前面介绍的术语,就是FFmpeg中抽象出来的概念。其中:
-
AVFormatContext:就是对容器或者媒体文件层次的抽象。
-
AVStream:在文件中(容器里面)包含了多路流(音频流、视频流、字幕流),AVStream 就是对流的抽象。
-
AVCodecContext 与 AVCodec:在每一路流中都会描述这路流的编码格式,对编解码器格式以及编解码器的抽象就是AVCodecContext 与 AVCodec。
-
AVPacket 与 AVFrame:对于编码器或者解码器的输入输出部分,也就是压缩数据以及原始数据的抽象就是AVPacket与AVFrame。
-
AVFilter:除了编解码之外,对音视频的处理肯定是针对于原始数据的处理,也就是针对AVFrame的处理,使用的就是AVFilter。
3.FFmpeg 通用 API 分析
av_register_all 分析
在最开始编译FFmpeg的时候,我们做了一个configure的配置,其中开启或者关闭了很多选项。configure的配置会生成两个文件:config.mk和config.h。
config.mk:就是makefile文件需要包含进去的子模块,会作用在编译阶段,帮助开发者编译出正确的库。
config.h:作用在运行阶段,主要是确定需要注册那些容器及编解码格式到FFmpeg框架中。
调用 av_register_all 就可以注册config.h里面开发的编解码器,然后会注册所有的Muxer和Demuxer(封装格式),最后注册所有的Protocol(协议)。
这样在configure时开启或者关闭的选项就作用到了运行时,该函数的源码分析设计的源码文件包括:url.c、allformats.c、mux.c、format.c 等文件。
av_find_codec 分析
这个方法包含了两部分的内容:一部分是寻找解码器,一部分是寻找编码器。其实在av_register_all的函数执行时,就已经把编码器和解码器都存放到一个链表中了。这里寻找编解码器就是从上一步构造的链表中遍历,通过Codec的ID或者name进行条件匹配,最终返回对于的Codec。
avcodec_open2 分析
该函数是打开编解码器(Codec)的函数,无论是编码过程还是解码过程,都会用到这个函数。该函数的输入参数有三个:第一个是AVCodecContext,解码过程由FFmpeg引擎填充,编码过程由开发者自己构造,如果想传入私有参数,则为它的priv_data设置参数;第二个参数是上一步通过av_find_codec寻找出来的编解码器(Codec);第三个参数一般传NULL。
avcodec_close 分析
如果理解了avcodec_open,那么对应的close就是一个逆过程,找到对应的实现文件中的close函数指针所只指向的函数,然后该函数会调用对应第三方库的API来关闭掉对应的编码库。
4.总结
本文主要是讲述了FFmpeg的相关术语,并讲解了一下通用的API的分析,不难看出其实FFmpeg所做的事情就是透明化所有的编解码库,用自己的封装来为开发者提供统一的接口。开发者使用不同的编码库时,只需要指明要用哪一个即可,这也充分体现了面向对象编程中的封装特性
九丶FFmpeg 编解码 API 分析
在上一篇文章中,我们简单的讲解了一下FFmpeg 的API基本概念,并分析了一下通用API,本文我们将分析 FFmpeg 在编解码时使用的API。
1.FFmpeg 解码 API 分析
1.1.avformat_open_input 分析
函数 avformat_open_input 会根据所提供的文件路径判断文件的格式,其实就是通过这一步来决定到底是使用哪个Demuxer。
举个例子:如果是flv,那么Demuxer就会使用对应的ff_flv_demuxer,所以对应的关键生命周期的方法read_header、read_packet、read_seek、read_close都会使用该flv的Demuxer中函数指针指定的函数。read_header会将AVStream结构体构造好,以方便后续的步骤继续使用AVStream作为输入参数。
1.2.avformat_find_stream_info 分析
该方法的作用就是把所有的Stream的MetaData信息填充好。方法内部会先查找对于的解码器,然后打开对应的解码器,紧接着会利用Demuxer中的read_packet函数读取一段数据进行解码,当然,解码的数据越多,分析出来的流信息就越准确,如果是本地资源,那么很快就可以得到准确的信息了。但是对于网络资源来说,则会比较慢,因此该函数有几个参数可以控制读取数据的长度,一个是probe size,一个是max_analyze_duration, 还有一个就是fps_probe_size,这三个参数共同控制解码数据的长度,如果配置的这几个参数的数值越小,那么这个函数执行的时间就会越快,但会导致AVStream结构体里面的信息(视频的宽、高、fps、编码类型)不准确。
1.3.av_read_frame 分析
该方法读取出来的数据是AVPacket,在FFmpeg的早期版本中开发给开发者的函数其实就是av_read_packet,但是需要开发者自己来处理AVPacket中的数据不能被解码器处理完的情况,即需要把未处理完的压缩数据缓存起来的问题。所以在新版本的FFmpeg中,提供了该函数,用于处理此状况。 该函数的实现首先会委托到Demuxer的read_packet方法中,当然read_packet通过解服用层和协议层的处理后,会将数据返回到这里,在该函数中进行数据缓冲处理。
对于音频流,一个AVPacket可能会包含多个AVFrame,但是对于一个视频流,一个AVPacket只包含一个AVFrame,该函数最终只会返回一个AVPacket结构体。
1.4.avcodec_decode分析
该方法包含了两部分内容:一部分是解码视频,一部分是解码音频。在上面的函数分析中,我们知道,解码是会委托给对应的解码器来实施的,在打开解码器的时候就找到了对应的解码器的实现,比如对于解码H264来讲,会找到ff_h264_decoder,其中会有对应的生命周期函数的实现,最重要的就是init,decode,close三个方法,分别对应于打开解码器、解码及关闭解码器的操作,而解码过程就是调用decode方法。
1.5.avformat_close_input 分析
该函数负责释放对应的资源,首先会调用对应的Demuxer中的生命周期read_close方法,然后释放掉,AVFormatContext,最后关闭文件或者远程网络链接。
2.FFmpeg 编码 API 分析
2.1.avformat_alloc_output_context2 分析
该函数内部需要调用方法avformat_alloc_context来分配一个AVFormatContext结构体,当然最关键的还是根据上一步注册的Muxer和Demuxer部分(也就是封装格式部分)去找对应的格式。有可能是flv格式、MP4格式、mov格式,甚至是MP3格式等,如果找不到对应的格式(应该是因为在configure选项中没有打开这个格式的开关),那么这里会返回找不到对于的格式的错误提示。在调用API的时候,可以使用av_err2str把返回的整数类型的错误代码转换为肉眼可读的字符串,这是个在调试中非常有用的工具函数。该函数最终会将找出来的格式赋值给AVFormatContext类型的oformat。
2.2avio_open2 分析
首先会调用函数ffurl_open,构造出URLContext结构体,这个结构体中包含了URLProtocol(需要去第一步register_protocol中已经注册的协议链表)中去寻找;接着会调用avio_alloc_contex方法,分配出AVIOContext结构体,并将上一步构造出来的URLProtocol传递进来;然后把上一步分配出来的AVIOContext结构体赋值给AVFormatContext属性。
下面就是针对上面的描述总结的结构之间的构架图,各位可以参考此图进行进一步的理解:
avio_open2的过程也恰好是在上面我们分析avformat_open_input过程的一个逆过程。编码过程和解码过程从逻辑上来讲,也是一个逆过程,所以在FFmpeg实现的过程中,他们也互为逆过程。
2.3.编码其他API(步骤)分析
编码的其他步骤也是解码的一个逆过程,解码过程中的avformat_find_stream_info对应到编码就是avformat_new_stream和avformat_write_header。
- avformat_new_stream函数会将音频流或者视频流的信息填充好,分配出AVStream结构体,在音频流中分配声道、采样率、表示格式、编码器等信息,在视频中分配宽、高、帧率、表示格式、编码器等信息。
- avformat_write_header函数与解码过程中的read_header恰好是一个逆过程,这里就不多赘述了。
接下来就是编码阶段了:
- 将手动封装好的AVFrame结构体,作为avcodec_encodec_video方法的输入,然后将其编码成为AVPacket,然后调用av_write_frame方法输出到媒体文件中。
- av_write_frame 方法会将编码后的AVPacket结构体作为Muxer中的write_packet生命周期方法的输入,write_packet会加上自己封装格式的头信息,然后调用协议层,写到本地文件或者网络服务器上。
- 最后一步就是av_write_trailer(该函数有一个非常大的坑,如果没执行write_header操作,就直接执行write_trailer操作,程序会直接Carsh掉,所以这两个函数必须成对出现),av_write_trailer会把没有输出的AVPacket全部丢给协议层去做输出,然后会调用Muxer的write_trailer生命周期方法(不同的格式,写出的尾部也不一样)。
3.FFmpeg 解码 API 超时设置
当视频流地址能打开,但是视频流中并没有流内容的时候,可能会导致整体执行流程阻塞在 avformat_open_input 或者 av_read_frame 方法上。
主要原因就是avformat_open_input 和av_read_frame 这两个方法是阻塞的。
av_read_frame() -> read_frame_internal() -> ff_read_packet() -> s->iformat->read_packet() -> read_from_url() -> ffurl_read() -> retry_transfer_wrapper() (此方法会堵塞)
虽然我们可以通过设置 ic->flags |= AVFMT_FLAG_NONBLOCK; 将操作设置为非阻塞,但这样设置是不推荐的,会导致后续的其他操作出现问题。
一般情况下,我们推荐另外两种机制进行设置:
3.1.设置开流的超时时间
在设置开流超时时间的时候,需要注意 不同的协议设置的方式是不一样的。
| 1
2
| 方法: timeout --> 单位:(http:ms udp:s)
方法:stimeout --> 单位:(rtsp us) | | --------------- | ------------------------------------------------------------ |
设置udp、http 超时的示例代码如下:
AVDictionary* opts = NULL;
av_dict_set(&opts, "timeout", "3000000", 0);//单位 如果是http:ms 如果是udp:s
int ret = avformat_open_input(&ctx, url, NULL, &opts);
设置rtsp超时的示例代码如下:
AVDictionary* opts = NULL;
av_dict_set(&opts, "rtsp_transport", m_bTcp ? "tcp" : "udp", 0); //设置tcp or udp,默认一般优先tcp再尝试udp
av_dict_set(&opts, "stimeout", "3000000", 0);//单位us 也就是这里设置的是3s
ret = avformat_open_input(&ctx, url, NULL, &opts);
3.2.设置interrupt_callback定义返回机制
设置回调,监控read超时情况,回调方法为:
int64_t lastReadPacktTime;
static int interrupt_cb(void *ctx)
int timeout = 3;
if (av_gettime() - lastReadPacktTime > timeout * 1000 * 1000)
return -1;
return 0;
回调函数中返回0则代表ffmpeg继续阻塞直到ffmpeg正常工作为止,否则就代表ffmpeg结束阻塞可以将操纵权交给用户线程并返回错误码。
对指定的 AVFormatContext 进行设置,并在需要调用的设置的时间之前,记录当前的时间,这样在回调的时候就能根据时间差,判断执行相应的逻辑:
avformat_open_input 设置方式:
inputContext = avformat_alloc_context();
lastReadPacktTime = av_gettime();
inputContext->interrupt_callback.callback = interrupt_cb;
int ret = avformat_open_input(&inputContext, inputUrl.c_str(), nullptr, nullptr);
av_read_frame 设置方式:
lastReadPacktTime = av_gettime();
ret = av_read_frame(inputContext, packet);
在实际开发中,只是设计这个机制,很容易出现超时,但如果超时时间设置过程,又容易阻塞线程。一般推荐的方案为:在超时的机制上增加连续读流的时长统计,当连续读流超时超过一定时间时就通知当前读流操作已失败。
<以上是关于音视频大合集第三篇;深入探索的主要内容,如果未能解决你的问题,请参考以下文章