音频相关
- 编译fdk_aac的编译参数
- 音频码率
=========================================================================
h264相关
yuv420p 颜色空间相关
-
4:4:4 表示不降低色度(UV)通道的采样率。每个 Y 分量对应一组 UV 分量。
-
4:2:2 表示 2:1 水平下采样,没有垂直下采样。每两个 Y 分量共享一组 UV 分量。
-
4:2:0 表示 2:1 水平下采样,同时 2:1 垂直下采样。每四个 Y 分量共享一组 UV 分量。
-
4:1:1 表示 4:1 水平下采样,没有垂直下采样。每四个 Y 分量共享一组 UV 分量。4:1:1 采样比其他格式少见
-
听这个听得懂:YUV图像格式介绍_哔哩哔哩_bilibili
这篇文章有参考价值:音视频开发学习:H.264/AVC视频编解码技术 - 知乎 (zhihu.com)
颜色空间参考:www.jianshu.com/p/358bf8b7e…
-
帧内预测是根据帧内已经编码的样本,为当前的样本计算出一个预测值,用当前样本值减去预测值得到一个残差值,目的就是为了减少传输的数据量。
-
NALU 以 0000 0001划分开
-
yuv420p一个pix占用字节数1.5Byte
rgb 8bit位深,3通道(不含透明度),一个pix占用3Bytes -
h264编码(pix:640*480 yuv420p fps=15 500kbps)常见压缩比1%
常见电影_fps>=60; 视频直播_fps>=15 -
码率经验值.参考https://docs,agora.io/cn
-
b帧多的缺点,占用cpu;解码耗时;不宜直播
实时:i+p;转码:大量b帧,为减小存储 -
IDR帧,特殊的I帧,解码立即刷新帧,由于每个GOP间明显的差别
- 特点:解码端遇到IDR会将缓存清空,重新解码
- 每个GOP中第一帧就是IDR
-
h264默认编码(不是编码顺序,可以看作gop中帧顺序)
IBBBPBBBPBBBI
B帧间无相互关系,B帧参考始终是之前的I和之后的P帧 -
SPS PPS 在每个IDR帧前都会成对出现这两种帧(参数术语)
- SPS 参数序列集(帧内参数,约束gop的参数)
SPS_ID 帧数 参考帧数量(可参考一帧,亦可参考多帧) 解码图像尺寸 编码模式 - PPS 图像参数集(约束GOP中每帧的参数)
ID 熵编码 帧编码 片组数目(帧编码) 初始量化参数 区块滤波系数
- SPS 参数序列集(帧内参数,约束gop的参数)
-
帧内压缩理论:
- 1.相邻像素差别不大,有宏块预测的基础
- 2.人眼对亮度的敏感超过色度
- 3.yuv分开存储,利于压缩
-
宏块预测有9种模式
-
残差值 = 原始图像 - 预测出的图像
压缩时,预测图象压缩+残差值压缩(补偿残差);主要用在帧内压缩
运动补偿:在解码时将残差值的影响考虑在内 -
运动估计:根据宏块匹配的手段找到运动矢量的过程称运动估计
-
宏块查找:目的是找到宏块的运动轨迹(运动矢量)
-
宏块查找算法:
三步搜索 二位对数搜索 四步搜索 钻石搜索 -
压缩编码的主要手段是:运动矢量+补偿压缩(残差值补偿),图像还原的原理也是依据这两点
-
花屏,马赛克原因:GOP中丢帧(主要丢失的是P,B帧,运动矢量,残差值也丢失)
-
花屏的兄弟-卡顿:当GOP丢帧时,就丢弃掉GOP内所有帧,直到下一个GOP的IDR帧到来;这种情况的刷新周期就取决于IDR帧间隔(I帧间隔) 卡顿和花屏不能兼得,互斥关系
-
无损压缩示意图
stateDiagram-v2
图像矩阵 --> 数据集中在矩阵中的一角 : DCT变换
数据集中在矩阵中的一角 --> 压缩后图像 : vlc压缩,mpeg2,类似哈夫曼算法,高频数据短编码
数据集中在矩阵中的一角 --> 压缩后图像 : CABAC,h264
graph LR
图像矩阵 --DCT变换-->
矩阵,数据集中在矩阵中的一角
--vlc压缩,mpeg2,类似哈夫曼算法,高频数据短编码-->
压缩后图像
矩阵,数据集中在矩阵中的一角
--CABAC,h264-->
压缩后图像
- ffmpeg_h264编码器参数设置
nalu
-
rtp header
-
rtp单包封装nalu,rtp payload 表示
-
rtp多包封装nalu,rtp payload 表示
-
大
nalu在rtp分包模式下(注意,与单包模式的区别):(153条消息) NALU数据打RTP包流程详解_谁便取名好难的博客-CSDN博客_nalu数据 -
NALU 的常见问题汇总-ZigZagSin (zzsin.com)
=======================================================================
读乔帮主 h264 编码博客
nalu 中的 slice 和 IDR 帧的对应:www.zzsin.com/article/avc…
大部分情况下,一个 slice 就是一帧
这就是为什么 IDR 帧通常只在流媒体格式(RTMP,RTP)中被强调,而在类似 MP4 之类的储存格式中,因为不必担心丢包等,所以往往只有开头一个 IDR 帧。
slice 是什么?www.zzsin.com/article/sli…
slice是否就是帧:www.zzsin.com/article/sli…
点出了slice, 帧, 宏块间的关系
首先要讲明白一点,那就是 Slice 不是帧。我们经常在博客中看到一些说法,例如:这个 NALU 是一个 I 帧,这个 NALU 是一个 P 帧。其实这些说法严格意义上来说都是不太对的。
在编码的时候,一帧图像首先会被分割成若干宏块,然后对每个宏块进行编码压缩,之后,会将宏块写入到 Slice 里面,最后再加上一些修饰变成 NALU。
这个过程中有一点要注意,那就是一帧图像产生宏块,不一定要写到一个 Slice 里面。
可以看到 slice_type 规定了 slice 的类型,这也解释了之前的一些误区。有人说根据 NALU 的类型就能判读是 I Slice 还是 P Slice 还是 B Slice。其实是错误的,要判断这些必须要读到 slice_type 才行。
帧内编码,帧内预测:www.zzsin.com/article/int…
=================================================================================
协议与传输
udp rtp rtcp
rtmp
参考干货:RTMP 协议:为什么直播推流协议都爱用它?丨音视频基础 (qq.com)
rtmps与c端的交互过程是比较复杂的,上面是chunk的格式,chunk之上是消息,消息.服务于数据封装,是 RTMP 协议中的基本数据单元;块,服务于网络传输
通过这种分层的设计,就可以将大的消息(Message)数据分包成小的块(Chunk)通过网络来进行传输,这个也是 RTMP 能够实现降低延时的核心原因下面介绍rtmp 消息格式
rtmp消息格式
rtmp chunk(并不是消息格式,再次确认)- fmt 两个bit, 00(metadata 和流刚开始时的绝对时间戳,音视频帧 ) 01(大多数情况) 11 三类是最常见的类型
rtmp就是在下面的tagdataflv_header
tag_headerlibrtmp转载packet(librtmp中的数据结构)
rtmp参考:mp.weixin.qq.com/s?__biz=MjM…- 通常的协议的实现会给不同类型的消息赋予不同的优先级,当传输能力受到限制时它会影响消息下层流发送的队列顺序。
- 问题:因为一个流当中可以交错传输多种消息类型的Chunk,那么多个Chunk怎么标记同属于同一类Message的呢?
答案:是通过Chunk Stream ID区分的,同一个Chunk Stream ID 必然属于同一个Message - chunk stream id (chunk中的字段)表明该 chunk 是从哪个 message 中切分出来的
- msg type id (chunk有这个字段,区分负载的音视频数据类型), 如视频为9或音频为8
- Message StreamID (RTMP Message, chunk都有这个字段)是音视频流的唯一ID, 一路流如果既有音频包又有视频包,那么这路流音频包的StreamID和他视频包的StreamID相同
- RTMP流中视频和音频拥有单独的Chunk Stream ID 比如音频的cs id=20,视频的cs id=21。
- 默认的Chunk Size(Chunk data 部分)是128字节
- RTMP Chunk Header-为什么存在不同长度? 一般情况下,msg stream id是不会变的,所以针对视频或音频, 除 了第一个RTMP Chunk Header是12Bytes的,后续即可采用8Bytes 的。 (5.3. Chunking) 如果消息的长度(message length)和类型(msg type id, 如视频为9或音 频为8)又相同,即可将这两部分也省去,RTMP Chunk Header采用 4Bytes类型的。 如果当前Chunk与之前的Chunk相比, msg stream id相同,msg type id相同,message length相同,而且都属于同一个消息(由同一个 Message切割成),这类Chunk的时间戳(timestamp)也是相同的,故后 续的也可以省去,RTMP Chunk Header采用1 Byte类型的。 当Chunk Size足够大时(一般不这么干),此时所有的Message都只能 相应切割成一个Chunk,该Chunk仅msg stream id相同。此时基本上 除了第一个Chunk的Header是12Bytes外,其它所有Chunk的Header都 是8Bytes
- 带宽和一帧数据量来估算传输耗时,大致评估出带宽
如果一帧1080P I帧数据量为244KBytes,假设带宽为10Mbit/s,传输一帧的 耗时为: 24410248/(1010241024) = 0.190625秒=190毫秒 假如要实时传输25帧的I帧文件,即允许每帧传输最大耗时为40毫秒,需要 带宽47.65625Mbit,这还没包括传输中的ACK数据。所以要把一帧切成Chunk 来传输,确保一些比较小的帧(音频帧,控制帧)能及时传到 - RTMP Chunk Stream层级没有优先级的划分,而是在高层次Message stream提供优先级的划分。 注:5. RTMP Chunk Stream 不同类型的消息会被分配不同的优先级,当网络传输能力受限时, 优先级用来控制消息在网络底层的排队顺序。 比如当客户端网络不佳时,流媒体服务器可能会选择丢弃视频消息, 以保证音频消息可及时送达客户端
- 有人用wireshark转包分析发现,rtmp流的chunk视频流(或音频流)除第 一个视频时间戳为绝对时间戳外,后续的时间戳均为timestamp delta,即当前时间戳与上一个时间戳的差值。
比如帧率为25帧/秒的视频流,timestamp delta基本上都为40ms
通常情况下,Chunk的时间戳(包括绝对时间戳和Timestamp delta) 是3个字节。 但时间戳值(比如时间戳差值ts delta)超过0xFFFFFF时,启用Extended Timestamp(4个字节) 来表示时间戳。 通常情况下 -- 3字节
=======================================================================================
lichao ffmpeg 精讲
播放器线程模型
yuv nv12 bgr 内存模型,颜色空间
=======================================================================
命令行
ffplay -pixel_format nv12 -f rawvideo -video_size 1920x1080 /mnt/sdb1/Projects/sdc/pre_post0.yuv
========================================================================
常用推流格式
- 值得参考:www.cnblogs.com/remember-fo…
- linux 桌面流推到 rtsp 服务器上
ffmpeg -video_size 1920x1080 -f x11grab -i :0.0 -framerate 25 -vcodec libx264 -rtsp_transport tcp -pix_fmt yuv420p -f rtsp rtsp://192.168.2.66/sivid - windows 桌面流推到 rtsp 服务器上
推到zlmediakitffmpeg -f gdigrab -i desktop -vcodec libx264 -preset ultrafast -framerate 25 -vcodec libx264 -rtsp_transport tcp -pix_fmt yuv420p -f rtsp rtsp://10.10.1.119:554/zhouffmpeg -f gdigrab -i desktop -vcodec libx264 -preset ultrafast -framerate 25 -vcodec libx264 -rtsp_transport tcp -pix_fmt yuv420p -f rtsp rtsp://10.10.1.209:554/live/zhou - windows 桌面推流到 rtmp 服务器上
推到zlmediakitffmpeg -f gdigrab -i desktop -vcodec libx264 -preset ultrafast -framerate 25 -vcodec libx264 -pix_fmt yuv420p -f flv rtmp://10.10.1.189:1935/live/test/zhou2ffmpeg -f gdigrab -i desktop -vcodec libx264 -preset ultrafast -framerate 25 -vcodec libx264 -pix_fmt yuv420p -f flv rtmp://10.10.1.209:1935/live/test/zhou1 - mp4文件推到 rtmp 服务器
ffmpeg -re -stream_loop -1 -i ./铁路覆盖3_crop.mp4 -vcodec libx264 -x264opts keyint=25 -an -f flv -y rtmp://10.10.1.209/live/zhou1 - windows 推单屏到 easyDarwin rtsp
ffmpeg -video_size 1920x1080 -f gdigrab -i desktop -vcodec libx264 -preset ultrafast -framerate 25 -vcodec libx264 -rtsp_transport tcp -pix_fmt yuv420p -f rtsp rtsp://192.168.2.38:666/live/test - linux desktop to rtsp server, +1920 check one screen in two
# echo display hardware echo $DISPLAY # select one screen :1.0 ffmpeg -video_size 1920x1080 -f x11grab -i :1.0+1920 -framerate 25 -vcodec libx264 -rtsp_transport tcp -pix_fmt yuv420p -f rtsp rtsp://10.10.1.189/zhou1 - 推到内网darwin服务器
ffmpeg -f gdigrab -i desktop -vcodec libx264 -preset ultrafast -framerate 25 -vcodec libx264 -rtsp_transport tcp -pix_fmt yuv420p -f rtsp rtsp://192.168.2.66/zhou - xiongmai
ffplay "rtsp://192.168.0.81:554/user=admin&password=&channel=0&stream=1.sdp?real_stream" - dahua:
ffplay "rtsp://admin:admin2019@192.168.0.123:554/cam/realmonitor?channel=1&subtype=0" dahua:ffplay "rtsp://admin:admin123456@192.168.0.108:554/cam/realmonitor?channel=1&subtype=0" - 高德:
ffplay "rtsp://admin:admin2019@192.168.0.30:554/cam/realmonitor?channel=1&subtype=0" - 艾睿:
ffplay "rtsp://192.168.0.25:554/stream0" - 其他一些测试的视频流
rtmp://58.200.131.2:1935/livetv/hunantv http://ivi.bupt.edu.cn/hls/cctv7hd.m3u8 ffmpeg -re -i "rtsp://192.168.0.81:554/user=admin&password=&channel=0&stream=1.sdp?real_stream" -vcodec h264 -acodec aac -f rtsp -rtsp_transport tcp rtsp://127.0.0.1/live/test //查看视频流参数 ffprobe -show_streams "rtsp://192.168.0.25:554/stream0" ffprobe -show_format "rtsp://192.168.0.25:554/stream0" ffprobe -i "rtsp://192.168.0.25:554/stream0"
博客对此解释很详细
www.cnblogs.com/viruscih/ar…
- xiongmai相机推向nginx服务器
ffmpeg -i "rtsp://192.168.0.86:554/user=admin&password=&channel=0&stream=1.sdp?real_stream" -c copy -flvflags no_duration_filesize -f flv rtmp://www.z-coding.com.cn/live/zhimakeji192.168.0.86 - 读取nginx-rtmp视频流
ffplay "http://www.z-coding.com.cn:8088/live?app=live&stream=zhimakeji192.168.0.85" - xiongmai相机推向ZLMediaServer
ffmpeg -i "rtsp://192.168.0.93:554/user=admin&password=&channel=0&stream=1.sdp?real_stream" -c copy -f rtsp -rtsp_transport udp rtsp://192.168.0.41:554/live/stream0 - 艾睿相机视频流推向MediaServer
ffmpeg -i "rtsp://192.168.0.25:554/stream0" -c copy -f rtsp -rtsp_transport udp rtsp://192.168.0.41:554/live/stream0 - 持续运行的bat
或者:run ffmpeg -i "rtsp://192.168.0.93:554/user=admin&password=&channel=0&stream=1.sdp?real_stream" -c copy -f rtsp -rtsp_transport udp rtsp://192.168.0.41:554/live/stream0 goto run:start ffmpeg -i "rtsp://192.168.0.93:554/user=admin&password=&channel=0&stream=1.sdp?real_stream" -c copy -f rtsp -rtsp_transport udp rtsp://192.168.0.41:554/live/stream0 run start - 将大华相机的h264裸流,不封装以udp格式推向ZLmediaKit服务器,注意-f输出格式,可指定成flv, udp可换作tcp
ffmpeg -i "rtsp://admin:admin2019@192.168.0.109:554/cam/realmonitor?channel=1&subtype=0" -c copy -f rtsp -rtsp_transport udp rtsp://192.168.0.57:554/live/z - 本地视频文件以rtsp推到达尔文服务器
ffmpeg -re -stream_loop -1 -i ./德州国鼎新型建材有限公司-4-车辆连续触发告警1.mp4 -vcodec copy -rtsp_transport tcp -f rtsp rtsp://10.10.1.189/zhou - 自动推流脚本
#!/bin/bash ffmpeg -re -stream_loop -1 -i $1 -vcodec copy -rtsp_transport tcp -f rtsp rtsp://$2/zhou
ZLMediaServer保存.ts切片文件,供客户使用
在线播放.m3u8的网址:
m3u8视频在线播放器 (m3u8player.top)
视频源格式:
http://192.168.0.41/live/stream0/hls.m3u8
ZLMediaServer服务器下的文件格式
与MediaServer同级目录下
/www/live/stream0/*.m3u8
把mp4 flv文件放入http服务的根目录下即www目录下,测试下点播是否成功
# 首先,艾睿相机的视频流保存为.mp4
ffmpeg -y -i rtsp://192.168.0.41:554/live/stream0 -vcodec copy -f mp4 test.mp4
在http服务端下放有test.MP4和test.flv及hsl.m3u8的文件,测试前端能否播放,以下三个文件均能在前端加载出视频 --> 点播ok
- mp4_play 前端原生支持mp4
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
<object width="500" height="300" data="http://192.168.0.41/test.mp4"></object>
</body>
</html>
- hls_play
<!DOCTYPE html>
<html>
<head>
<title>播放器</title>
</head>
<body>
<script src="https://cdn.jsdelivr.net/hls.js/latest/hls.min.js"></script>
<video id="video"></video>
<script>
if(Hls.isSupported()) {
var video = document.getElementById('video');
var hls = new Hls();
hls.loadSource('http://192.168.0.41/live/stream0/hls.m3u8');
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED,function() {
video.play();
});
}
</script>
</body>
</html>
- flv_player 需要在线加载flv.js
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<title>flv.js demo</title>
<style>
.mainContainer {
display: block;
width: 1024px;
margin-left: auto;
margin-right: auto;
}
.urlInput {
display: block;
width: 100%;
margin-left: auto;
margin-right: auto;
margin-top: 8px;
margin-bottom: 8px;
}
.centeredVideo {
display: block;
width: 100%;
height: 576px;
margin-left: auto;
margin-right: auto;
margin-bottom: auto;
}
.controls {
display: block;
width: 100%;
text-align: left;
margin-left: auto;
margin-right: auto;
}
</style>
</head>
<body>
<div class="mainContainer">
<video id="videoElement" class="centeredVideo" controls autoplay width="1024" height="576">Your browser is too old which doesn't support HTML5 video.</video>
</div>
<br>
<div class="controls">
<!--<button onclick="flv_load()">加载</button>-->
<button onclick="flv_start()">开始</button>
<button onclick="flv_pause()">暂停</button>
<button onclick="flv_destroy()">停止</button>
<input style="width:100px" type="text" name="seekpoint" />
<button onclick="flv_seekto()">跳转</button>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/flv.js/1.5.0/flv.js"></script>
<script>
var player = document.getElementById('videoElement');
if (flvjs.isSupported()) {
var flvPlayer = flvjs.createPlayer({
type: 'flv',
url: 'http://192.168.0.41/test.flv'
});
flvPlayer.attachMediaElement(videoElement);
flvPlayer.load(); //加载
}
function flv_start() {
player.play();
}
function flv_pause() {
player.pause();
}
function flv_destroy() {
player.pause();
player.unload();
player.detachMediaElement();
player.destroy();
player = null;
}
function flv_seekto() {
player.currentTime = parseFloat(document.getElementsByName('seekpoint')[0].value);
}
</script>
</body>
</html>
- 推流的反馈信息
C:\Users\zhouqinan>ffmpeg -i "rtsp://192.168.0.93:554/user=admin&password=&channel=0&stream=1.sdp?real_stream" -c copy -f rtsp -rtsp_transport udp rtsp://192.168.0.41:554/live/stream3
ffmpeg version 4.3.1 Copyright (c) 2000-2020 the FFmpeg developers
built with gcc 10.2.1 (GCC) 20200726
configuration: --disable-static --enable-shared --enable-gpl --enable-version3 --enable-sdl2 --enable-fontconfig --enable-gnutls --enable-iconv --enable-libass --enable-libdav1d --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libopus --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libsrt --enable-libtheora --enable-libtwolame --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libzimg --enable-lzma --enable-zlib --enable-gmp --enable-libvidstab --enable-libvmaf --enable-libvorbis --enable-libvo-amrwbenc --enable-libmysofa --enable-libspeex --enable-libxvid --enable-libaom --enable-libgsm --enable-librav1e --disable-w32threads --enable-libmfx --enable-ffnvcodec --enable-cuda-llvm --enable-cuvid --enable-d3d11va --enable-nvenc --enable-nvdec --enable-dxva2 --enable-avisynth --enable-libopenmpt --enable-amf
libavutil 56. 51.100 / 56. 51.100
libavcodec 58. 91.100 / 58. 91.100
libavformat 58. 45.100 / 58. 45.100
libavdevice 58. 10.100 / 58. 10.100
libavfilter 7. 85.100 / 7. 85.100
libswscale 5. 7.100 / 5. 7.100
libswresample 3. 7.100 / 3. 7.100
libpostproc 55. 7.100 / 55. 7.100
Guessed Channel Layout for Input Stream #0.1 : mono
Input #0, rtsp, from 'rtsp://192.168.0.93:554/user=admin&password=&channel=0&stream=1.sdp?real_stream':
Metadata:
title : RTSP Session
Duration: N/A, start: 0.000000, bitrate: N/A
Stream #0:0: Video: h264 (Main), yuv420p(progressive), 704x576, 25 fps, 25 tbr, 90k tbn, 50 tbc
Stream #0:1: Audio: pcm_alaw, 8000 Hz, mono, s16, 64 kb/s
Output #0, rtsp, to 'rtsp://192.168.0.41:554/live/stream3':
Metadata:
title : RTSP Session
encoder : Lavf58.45.100
Stream #0:0: Video: h264 (Main), yuv420p(progressive), 704x576, q=2-31, 25 fps, 25 tbr, 90k tbn, 25 tbc
Stream #0:1: Audio: pcm_alaw, 8000 Hz, mono, s16, 64 kb/s
Stream mapping:
Stream #0:0 -> #0:0 (copy)
Stream #0:1 -> #0:1 (copy)
Press [q] to stop, [?] for help
[rtsp @ 0000028b22694180] Non-monotonous DTS in output stream 0:0; previous: 21600, current: 3600; changing to 21601. This may result in incorrect timestamps in the output file.
[rtsp @ 0000028b22694180] Non-monotonous DTS in output stream 0:0; previous: 21601, current: 7200; changing to 21602. This may result in incorrect timestamps in the output file.
[rtsp @ 0000028b22694180] Non-monotonous DTS in output stream 0:0; previous: 21602, current: 10800; changing to 21603. This may result in incorrect timestamps in the output file.
[rtsp @ 0000028b22694180] Non-monotonous DTS in output stream 0:0; previous: 21603, current: 14400; changing to 21604. This may result in incorrect timestamps in the output file.
[rtsp @ 0000028b22694180] Non-monotonous DTS in output stream 0:0; previous: 21604, current: 18000; changing to 21605. This may result in incorrect timestamps in the output file.
[rtsp @ 0000028b22694180] Non-monotonous DTS in output stream 0:0; previous: 21605, current: 21600; changing to 21606. This may result in incorrect timestamps in the output file.
frame= 114 fps= 34 q=-1.0 Lsize=N/A time=00:00:04.56 bitrate=N/A speed=1.38x
video:151kB audio:36kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown
Exiting normally, received signal 2.
=========================================================================
编程实践
公网测试流rtsp://34.227.104.115:554/vod/mp4:BigBuckBunny_115k.mp4
lichao, 打开 ios 设备音频输入,不编码,原始音频数据重采样保存到 pcm 文件上
//
// testc.c
// myapp
//
// Created by lichao on 2020/1/30.
// Copyright © 2020年 lichao. All rights reserved.
//
#include "testc.h"
#include <string.h>
static int rec_status = 0;
void set_status(int status){
rec_status = status;
}
SwrContext* init_swr(){
SwrContext *swr_ctx = NULL;
//channel, number/
swr_ctx = swr_alloc_set_opts(NULL, //ctx
AV_CH_LAYOUT_STEREO, //输出channel布局
AV_SAMPLE_FMT_S16, //输出的采样格式
44100, //采样率
AV_CH_LAYOUT_STEREO, //输入channel布局
AV_SAMPLE_FMT_FLT, //输入的采样格式
44100, //输入的采样率
0, NULL);
if(!swr_ctx){
}
if(swr_init(swr_ctx) < 0){
}
return swr_ctx;
}
void rec_audio() {
int ret = 0;
char errors[1024] = {0, };
//重采样缓冲区
uint8_t **src_data = NULL;
int src_linesize = 0;
uint8_t **dst_data = NULL;
int dst_linesize = 0;
//ctx
AVFormatContext *fmt_ctx = NULL;
AVDictionary *options = NULL;
//pakcet
AVPacket pkt;
//[[video device]:[audio device]]
char *devicename = ":0";
//set log level
av_log_set_level(AV_LOG_DEBUG);
//start record
rec_status = 1;
//register audio device
avdevice_register_all();
//get format
AVInputFormat *iformat = av_find_input_format("avfoundation");
//open device
if((ret = avformat_open_input(&fmt_ctx, devicename, iformat, &options)) < 0 ){
av_strerror(ret, errors, 1024);
fprintf(stderr, "Failed to open audio device, [%d]%s\n", ret, errors);
return;
}
//create file
char *out = "/Users/lichao/Downloads/av_base/audio.pcm";
FILE *outfile = fopen(out, "wb+");
SwrContext* swr_ctx = init_swr();
//4096/4=1024/2=512
//创建输入缓冲区
av_samples_alloc_array_and_samples(&src_data, //输出缓冲区地址
&src_linesize, //缓冲区的大小
2, //通道个数
512, //单通道采样个数
AV_SAMPLE_FMT_FLT, //采样格式
0);
//创建输出缓冲区
av_samples_alloc_array_and_samples(&dst_data, //输出缓冲区地址
&dst_linesize, //缓冲区的大小
2, //通道个数
512, //单通道采样个数
AV_SAMPLE_FMT_S16, //采样格式
0);
//read data from device
while((ret = av_read_frame(fmt_ctx, &pkt)) == 0 &&
rec_status) {
av_log(NULL, AV_LOG_INFO,
"packet size is %d(%p)\n",
pkt.size, pkt.data);
//进行内存拷贝,按字节拷贝的
memcpy((void*)src_data[0], (void*)pkt.data, pkt.size);
//重采样
swr_convert(swr_ctx, //重采样的上下文
dst_data, //输出结果缓冲区
512, //每个通道的采样数
(const uint8_t **)src_data, //输入缓冲区
512); //输入单个通道的采样数
//write file
//fwrite(pkt.data, 1, pkt.size, outfile);
fwrite(dst_data[0], 1, dst_linesize, outfile);
fflush(outfile);
av_packet_unref(&pkt); //release pkt
}
//close file
fclose(outfile);
//释放输入输出缓冲区
if(src_data){
av_freep(&src_data[0]);
}
av_freep(&src_data);
if(dst_data){
av_freep(&dst_data[0]);
}
av_freep(&dst_data);
//释放重采样的上下文
swr_free(&swr_ctx);
//close device and release ctx
avformat_close_input(&fmt_ctx);
av_log(NULL, AV_LOG_DEBUG, "finish!\n");
return;
}
#if 0
int main(int argc, char *argv[])
{
rec_audio();
return 0;
}
#endif
lichao, 打开 ios 设备音频输入,重采样后编码成 aac 文件
//
// testc.c
// myapp
//
// Created by lichao on 2020/1/30.
// Copyright © 2020年 lichao. All rights reserved.
//
#include "testc.h"
#include <string.h>
static int rec_status = 0;
void set_status(int status){
rec_status = status;
}
//[in]
//[out]
//ret
//@brief encode audio data
static void encode(AVCodecContext *ctx,
AVFrame *frame,
AVPacket *pkt,
FILE *output){
int ret = 0;
//将数据送编码器
ret = avcodec_send_frame(ctx, frame);
//如果ret>=0说明数据设置成功
while(ret >= 0){
//获取编码后的音频数据,如果成功,需要重复获取,直到失败为止
ret = avcodec_receive_packet(ctx, pkt);
if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF){
return;
}else if( ret < 0){
printf("Error, encoding audio frame\n");
exit(-1);
}
//write file
fwrite(pkt->data, 1, pkt->size, output);
fflush(output);
}
return;
}
//[in]
//[out]
//
static AVCodecContext* open_coder(){
//打开编码器
//avcodec_find_encoder(AV_CODEC_ID_AAC);
AVCodec *codec = avcodec_find_encoder_by_name("libfdk_aac");
//创建 codec 上下文
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
codec_ctx->sample_fmt = AV_SAMPLE_FMT_S16; //输入音频的采样大小
codec_ctx->channel_layout = AV_CH_LAYOUT_STEREO; //输入音频的channel layout
codec_ctx->channels = 2; //输入音频 channel 个数
codec_ctx->sample_rate = 44100; //输入音频的采样率
codec_ctx->bit_rate = 0; //AAC_LC: 128K, AAC HE: 64K, AAC HE V2: 32K
codec_ctx->profile = FF_PROFILE_AAC_HE_V2; //阅读 ffmpeg 代码
//打开编码器
if(avcodec_open2(codec_ctx, codec, NULL)<0){
//
return NULL;
}
return codec_ctx;
}
static
SwrContext* init_swr(){
SwrContext *swr_ctx = NULL;
//channel, number/
swr_ctx = swr_alloc_set_opts(NULL, //ctx
AV_CH_LAYOUT_STEREO, //输出channel布局
AV_SAMPLE_FMT_S16, //输出的采样格式
44100, //采样率
AV_CH_LAYOUT_STEREO, //输入channel布局
AV_SAMPLE_FMT_FLT, //输入的采样格式
44100, //输入的采样率
0, NULL);
if(!swr_ctx){
}
if(swr_init(swr_ctx) < 0){
}
return swr_ctx;
}
/**
* @brief open audio device
* @return succ: AVFormatContext*, fail: NULL
*/
static
AVFormatContext* open_dev(){
int ret = 0;
char errors[1024] = {0, };
//ctx
AVFormatContext *fmt_ctx = NULL;
AVDictionary *options = NULL;
//[[video device]:[audio device]]
char *devicename = ":0";
//get format
AVInputFormat *iformat = av_find_input_format("avfoundation");
//open device
if((ret = avformat_open_input(&fmt_ctx, devicename, iformat, &options)) < 0 ){
av_strerror(ret, errors, 1024);
fprintf(stderr, "Failed to open audio device, [%d]%s\n", ret, errors);
return NULL;
}
return fmt_ctx;
}
/**
* @brief xxxx
* @return xxx
*/
static
AVFrame* create_frame(){
AVFrame *frame = NULL;
//音频输入数据
frame = av_frame_alloc();
if(!frame){
printf("Error, No Memory!\n");
goto __ERROR;
}
//set parameters
frame->nb_samples = 512; //单通道一个音频帧的采样数
frame->format = AV_SAMPLE_FMT_S16; //每个采样的大小
frame->channel_layout = AV_CH_LAYOUT_STEREO; //channel layout
//alloc inner memory
av_frame_get_buffer(frame, 0); // 512 * 2 * = 2048
if(!frame->data[0]){
printf("Error, Failed to alloc buf in frame!\n");
//内存泄漏
goto __ERROR;
}
return frame;
__ERROR:
if(frame){
av_frame_free(&frame);
}
return NULL;
}
static
void alloc_data_4_resample(uint8_t ***src_data,
int *src_linesize,
uint8_t *** dst_data,
int *dst_linesize){
//4096/4=1024/2=512
//创建输入缓冲区
av_samples_alloc_array_and_samples(src_data, //输出缓冲区地址
src_linesize, //缓冲区的大小
2, //通道个数
512, //单通道采样个数
AV_SAMPLE_FMT_FLT, //采样格式
0);
//创建输出缓冲区
av_samples_alloc_array_and_samples(dst_data, //输出缓冲区地址
dst_linesize, //缓冲区的大小
2, //通道个数
512, //单通道采样个数
AV_SAMPLE_FMT_S16, //采样格式
0);
}
/**
*/
static
void free_data_4_resample(uint8_t **src_data, uint8_t **dst_data){
//释放输入输出缓冲区
if(src_data){
av_freep(&src_data[0]);
}
av_freep(&src_data);
if(dst_data){
av_freep(&dst_data[0]);
}
av_freep(&dst_data);
}
/**
*/
static
void read_data_and_encode(AVFormatContext *fmt_ctx, //
AVCodecContext *c_ctx,
SwrContext* swr_ctx,
FILE *outfile){
int ret = 0;
//pakcet
AVPacket pkt;
AVFrame *frame = NULL;
AVPacket *newpkt = NULL;
//重采样缓冲区
uint8_t **src_data = NULL;
int src_linesize = 0;
uint8_t **dst_data = NULL;
int dst_linesize = 0;
frame = create_frame();
if(!frame){
//printf(...)
goto __ERROR;
}
newpkt = av_packet_alloc(); //分配编码后的数据空间
if(!newpkt){
printf("Error, Failed to alloc buf in frame!\n");
goto __ERROR;
}
//分配重采样输入/输出缓冲区
alloc_data_4_resample(&src_data, &src_linesize, &dst_data, &dst_linesize);
//read data from device
while((ret = av_read_frame(fmt_ctx, &pkt)) == 0 && rec_status) {
//进行内存拷贝,按字节拷贝的
memcpy((void*)src_data[0], (void*)pkt.data, pkt.size);
//重采样
swr_convert(swr_ctx, //重采样的上下文
dst_data, //输出结果缓冲区
512, //每个通道的采样数
(const uint8_t **)src_data, //输入缓冲区
512); //输入单个通道的采样数
//将重采样的数据拷贝到 frame 中
memcpy((void *)frame->data[0], dst_data[0], dst_linesize);
//encode
encode(c_ctx, frame, newpkt, outfile);
//
av_packet_unref(&pkt); //release pkt
}
//强制将编码器缓冲区中的音频进行编码输出
encode(c_ctx, NULL, newpkt, outfile);
__ERROR:
//释放 AVFrame 和 AVPacket
if(frame){
av_frame_free(&frame);
}
if(newpkt){
av_packet_free(&newpkt);
}
//释放重采样缓冲区
free_data_4_resample(src_data, dst_data);
}
void rec_audio() {
//context
AVFormatContext *fmt_ctx = NULL;
AVCodecContext *c_ctx = NULL;
SwrContext* swr_ctx = NULL;
//set log level
av_log_set_level(AV_LOG_DEBUG);
//register audio device
avdevice_register_all();
//start record
rec_status = 1;
//create file
//char *out = "/Users/lichao/Downloads/av_base/audio.pcm";
char *out = "/Users/lichao/Downloads/av_base/audio.aac";
FILE *outfile = fopen(out, "wb+");
if(!outfile){
printf("Error, Failed to open file!\n");
goto __ERROR;
}
//打开设备
fmt_ctx = open_dev();
if(!fmt_ctx){
printf("Error, Failed to open device!\n");
goto __ERROR;
}
//打开编码器上下文
c_ctx = open_coder();
if(!c_ctx){
printf("...");
goto __ERROR;
}
//初始化重采样上下文
swr_ctx = init_swr();
if(!swr_ctx){
printf("Error, Failed to alloc buf in frame!\n");
goto __ERROR;
}
//encode
read_data_and_encode(fmt_ctx, c_ctx, swr_ctx, outfile);
__ERROR:
//释放重采样的上下文
if(swr_ctx){
swr_free(&swr_ctx);
}
if(c_ctx){
avcodec_free_context(&c_ctx);
}
//close device and release ctx
if(fmt_ctx) {
avformat_close_input(&fmt_ctx);
}
if(outfile){
//close file
fclose(outfile);
}
av_log(NULL, AV_LOG_DEBUG, "finish!\n");
return;
}
#if 0
int main(int argc, char *argv[])
{
rec_audio();
return 0;
}
#endif
首先不知道ffmpeg中AVDictionary怎么设置(这个上下文是自己创建的还是ffmpeg中的变量,从代码上看不是自己申请的);而且还不知道向哪个上下文设置,是AVFormatContext还是AVCodec?也不知道有哪些键值对可以设置?
参考博客:FFMPEG Tips (5) 如何利用 AVDictionary 配置参数 - 知乎 (zhihu.com)
自己总结的三个文件可以看到设置的健有哪些,类型为AVOption的静态数组
- libavcodec\options_table.h
- libavformat\options_table.h
- libavformat\rtsp.c
- 比如有这些设置
案例一:void FFmpegThread::initOption() { //设置缓存大小,1080p可将值调大 av_dict_set(&options, "buffer_size", "8192000", 0); //以tcp方式打开,如果以udp方式打开将tcp替换为udp av_dict_set(&options, "rtsp_transport", transport.toLatin1().constData(), 0); //设置超时断开连接时间,单位微秒,3000000表示3秒 av_dict_set(&options, "stimeout", "3000000", 0); //设置最大时延,单位微秒,1000000表示1秒 av_dict_set(&options, "max_delay", "1000000", 0); //自动开启线程数 av_dict_set(&options, "threads", "auto", 0); //设置USB摄像机分辨率 if (url.startsWith("video")) { QString size = QString("%1x%2").arg(videoWidth).arg(videoHeight); av_dict_set(&options, "video_size", size.toLatin1().constData(), 0); } } bool FFmpegThread::initInput() { //实例化格式处理上下文 formatCtx = avformat_alloc_context(); //先判断是否是本地设备(video=设备名字符串),打开的方式不一样 int result = -1; if (url.startsWith("video")) { #if defined(Q_OS_WIN) AVInputFormat *ifmt = av_find_input_format("dshow"); #elif defined(Q_OS_LINUX) AVInputFormat *ifmt = av_find_input_format("video4linux2"); #elif defined(Q_OS_MAC) AVInputFormat *ifmt = av_find_input_format("avfoundation"); #endif result = avformat_open_input(&formatCtx, url.toStdString().data(), ifmt, &options); } else { result = avformat_open_input(&formatCtx, url.toStdString().data(), NULL, &options); } if (result < 0) { qDebug() << TIMEMS << "open input error" << url; return false; } //释放设置参数 if (options != NULL) { av_dict_free(&options); } //获取流信息 result = avformat_find_stream_info(formatCtx, NULL); if (result < 0) { qDebug() << TIMEMS << "find stream info error"; return false; } return true; }
yuv420sp(前置条件:要知到是 nv12 还是 nv21,以华为sdc相机为例,直接拿到 nv21) 转成 bgr,转得正确否以实际imwrite得到的图像为准,这么测试,一下就知道了
auto rows = input.height;
auto cols = input.width;
cv::Mat yuv420sp(rows * 3 / 2, cols, CV_8UC1, input.data[0]);
cv::Mat bgr(input.height, input.width, CV_8UC3);
cv::cvtColor(yuv420sp, bgr, cv::COLOR_YUV2BGR_NV21);
cv::imwrite("aft_post.jpg", bgr);
要想得到nv21,曲线救国: .jpg --> I420 --> 手工调整 u v 分量(把uuu vvv 平行,调整成 vuvuvu 交错)
static void TransFormJpg2Yuv(const cv::Mat &mat_bgr, unsigned char *yuv_nv21)
{
int w = mat_bgr.cols;
int h = mat_bgr.rows;
int wh = w * h;
int uv_len = wh / 2;
uint8_t *yuv = (uint8_t *)malloc(uv_len);
int imageLength = wh * 3 / 2;
cv::Mat out;
cv::cvtColor(mat_bgr, out, cv::COLOR_BGR2YUV_IYUV);
memcpy(yuv_nv21, out.data, wh * sizeof(unsigned char));
memcpy(yuv, out.data + wh, uv_len * sizeof(unsigned char));
int num = 0;
for (int j = wh; j != imageLength; j += 2)
{
yuv_nv21[j + 1] = yuv[num];
yuv_nv21[j] = yuv[wh / 4 + num];
++num;
}
free(yuv);
}
static bool Adjust(const cv::Mat& mat_bgr, std::vector<uint8_t>& vec_yuv)
{
if (mat_bgr.empty())
{
std::cout<<"read image failed !"<<std::endl;
return -1;
}
uint64_t w = 0;
uint64_t h = 0;
if (mat_bgr.cols % 16 != 0 || mat_bgr.rows % 16 != 0)
{
w = mat_bgr.cols / 16 * 16;
h = mat_bgr.rows / 16 * 16;
cv::resize(mat_bgr, mat_bgr, cv::Size(w, h));
}
else
{
w = mat_bgr.cols;
h = mat_bgr.rows;
}
vec_yuv.resize(w * h * 3 / 2);
TransFormJpg2Yuv(mat_bgr, &vec_yuv[0]);
return 0;
}
熟悉了 yuv420p 和 nv12(yuv420sp的一种)的结构后自己实现,bgr --> nv12
// utils
bool Align(const cv::Mat& img, cv::Mat& img_align, uint8_t align = 16)
{
if (img.empty())
{
AI_Log("DevFrame", KLERROR, "in Align input img is empty");
return false;
}
uint64_t w = 0;
uint64_t h = 0;
if (img.cols % align != 0 || img.rows % align != 0)
{
w = img.cols / align * align;
h = img.rows / align * align;
cv::resize(img, img_align, cv::Size(w, h));
}
else img_align = img;
return true;
}
static void BGR2NV12(const cv::Mat& img, std::unique_ptr<uint8_t[]>& unp_data)
{
auto w = img.cols;
auto h = img.rows;
auto wh = w * h;
auto img_size = wh * 3 / 2;
uint8_t* p_nv12 = new uint8_t[img_size];
unp_data.reset(p_nv12);
cv::Mat yuv420p;
// Enum value, cv::COLOR_BGR2YUV_IYUV same as cv::COLOR_BGR2YUV_I420
cv::cvtColor(img, yuv420p, cv::COLOR_BGR2YUV_IYUV);
if(!yuv420p.isContinuous())
{
yuv420p = yuv420p.clone();
}
uint8_t* p_yuv420p = yuv420p.data;
// Copy Y
memcpy(p_nv12, p_yuv420p, wh);
// Copy uv
uint32_t uv_offset = wh;
uint32_t num = 0;
for (int j = wh; j != img_size; j += 2)
{
p_nv12[j] = p_yuv420p[uv_offset + num];
p_nv12[j + 1] = p_yuv420p[uv_offset + wh / 4 + num];
++num;
}
}
librtmp puller
- RTMP_ReadPacket需要多次调用(每次读取一个chunk),并配合RTMPPacket_IsReady这个宏使用才能读取一个完整的RTMPPacket
与RTMP_SendPacket不同,RTMP_ReadPacket需要多次调用(每次读取一个chunk),并配合RTMPPacket_IsReady这个宏使用才能读取一个完整的RTMPPacket。 - www.cnblogs.com/chef/archiv…
- blog.csdn.net/yibu_refres…
- blog.csdn.net/xuerongdeng…
- mp.weixin.qq.com/s?__biz=MjM…
- www.zzsin.com/article/avc…
#include "rtmp_imple.h"
#include "ai_log.h"
RtmpImple::~RtmpImple()
{
RTMPPacket_Free(&packet_);
}
bool RtmpImple::Init(const std::string& url, uint32_t time_out)
{
url_ = url;
bool flag_init_success{false};
do
{
sp_rtmp_.reset(RTMP_Alloc(), [](RTMP* p_rtmp){ RTMP_Close(p_rtmp);
RTMP_Free(p_rtmp);
});
if(!sp_rtmp_.get())
{
AI_Log("RtmpImple", KLERROR, " alloc RTMP context failed");
break;
}
RTMP_Init(sp_rtmp_.get());
if(FALSE == RTMP_SetupURL(sp_rtmp_.get(), url_.c_str()))
{
AI_Log("RtmpImple", KLERROR, " RTMP set server url:{} failed", url_);
break;
}
sp_rtmp_->Link.timeout = time_out;
sp_rtmp_->Link.lFlags |= RTMP_LF_LIVE;
flag_init_success = true;
} while (false);
flag_init_success ? AI_Log("RtmpImple", KLINFO, " RtmpImple init success, url:{} ", url_) : AI_Log("RtmpImple", KLERROR, " RtmpImple init failed, url:{} ", url_);
return flag_init_success;
}
bool RtmpImple::Connect()
{
auto flag_connected{false};
do
{
if(FALSE == RTMP_Connect(sp_rtmp_.get(), NULL))
{
AI_Log("RtmpImple", KLERROR, " RTMP try connect server {} failed", url_);
break;
}
if(FALSE == RTMP_ConnectStream(sp_rtmp_.get(), 0))
{
AI_Log("RtmpImple", KLERROR, " RTMP connect stream failed, url:{}", url_);
break;
}
if(FALSE == RTMP_IsConnected(sp_rtmp_.get()))
{
AI_Log("RtmpImple", KLERROR, " RTMP check connect failed, url:{}", url_);
break;
}
flag_connected = true;
} while (false);
flag_connected ? AI_Log("RtmpImple", KLINFO, " RtmpImple connect success, url:{}", url_) : AI_Log("RtmpImple", KLERROR, " RtmpImple connect failed, url:{}", url_);
if(flag_connected)
{
// init codec
call_back_hanlde_codec_("H264/90000");
}
return flag_connected;
}
bool RtmpImple::CheckConneted()
{
auto result = RTMP_IsConnected(sp_rtmp_.get());
auto ret = (result == TRUE)? true : false;
return ret;
}
bool RtmpImple::GetNALU()
{
RTMPPacket_Reset(&packet_);
packet_.m_body = NULL;
packet_.m_chunk = NULL;
while (RTMP_IsConnected(sp_rtmp_.get()) && (TRUE == RTMP_ReadPacket(sp_rtmp_.get(), &packet_)))
{
if (!RTMPPacket_IsReady(&packet_)) continue;
else break;
}
auto result = RTMP_ClientPacket(sp_rtmp_.get(), &packet_);
auto ret = (TRUE==result)? true: false;
if(!ret) return false;
if (packet_.m_packetType == RTMP_PACKET_TYPE_VIDEO)
{
bool keyframe = 0x17 == packet_.m_body[0] ? true : false;
bool sequence = 0x00 == packet_.m_body[1];
// printf("keyframe=%s, sequence=%s\n", keyframe ? "true" : "false", sequence ? "true" : "false");
const auto len_nalu_header = sizeof(nalu_header_) / sizeof(uint8_t);
// SPS/PPS sequence
if (sequence)
{
uint32_t offset = 10;
uint32_t sps_len{0};
uint32_t sps_num = packet_.m_body[offset++] & 0x1f;
std::shared_ptr<uint8_t[]> sp_buf_sps;
for (int i = 0; i < sps_num; i++)
{
uint8_t ch0 = packet_.m_body[offset];
uint8_t ch1 = packet_.m_body[offset + 1];
sps_len = ((ch0 << 8) | ch1);
offset += 2;
// Write sps data
// fwrite(nalu_header, sizeof(uint8_t), 4, _file_ptr);
// fwrite(packet.m_body + offset, sizeof(uint8_t), sps_len, _file_ptr);
sp_buf_sps = std::shared_ptr<uint8_t[]>(new uint8_t[len_nalu_header + sps_len]);
memcpy(sp_buf_sps.get(), &nalu_header_[0], len_nalu_header);
memcpy(sp_buf_sps.get() + len_nalu_header, packet_.m_body + offset, sps_len);
offset += sps_len;
}
AI_Log("RtmpImple", KLINFO, " sps_num:{}", sps_num);
uint32_t pps_len{0};
uint32_t pps_num = packet_.m_body[offset++] & 0x1f;
std::shared_ptr<uint8_t[]> sp_buf_pps;
for (int i = 0; i < pps_num; i++)
{
uint8_t ch0 = packet_.m_body[offset];
uint8_t ch1 = packet_.m_body[offset + 1];
pps_len = ((ch0 << 8) | ch1);
offset += 2;
// Write pps data
// fwrite(nalu_header, sizeof(uint8_t), 4, _file_ptr);
// fwrite(packet.m_body + offset, sizeof(uint8_t), pps_len, _file_ptr);
sp_buf_pps = std::shared_ptr<uint8_t[]>(new uint8_t [len_nalu_header + pps_len]);
memcpy(sp_buf_pps.get(), &nalu_header_[0], len_nalu_header);
memcpy(sp_buf_pps.get() + len_nalu_header, packet_.m_body + offset, pps_len);
offset += pps_len;
}
auto len_sps_pps_2naluheader = sps_len + pps_len + 2*len_nalu_header;
std::shared_ptr<uint8_t[]> sp_buf(new uint8_t[len_sps_pps_2naluheader]);
memcpy(sp_buf.get(), sp_buf_sps.get(), sps_len + len_nalu_header);
memcpy(sp_buf.get() + sps_len + len_nalu_header, sp_buf_pps.get(), pps_len + len_nalu_header);
call_back_hanlde_sps_pps_(sp_buf.get(), len_sps_pps_2naluheader, 0, 0);
AI_Log("RtmpImple", KLINFO, " pps_num:{}", pps_num);
}
// Nalu frames
else
{
uint32_t offset = 5;
uint8_t ch0 = packet_.m_body[offset];
uint8_t ch1 = packet_.m_body[offset + 1];
uint8_t ch2 = packet_.m_body[offset + 2];
uint8_t ch3 = packet_.m_body[offset + 3];
uint32_t data_len = ((ch0 << 24) | (ch1 << 16) | (ch2 << 8) | ch3);
offset += 4;
// Write nalu data(already started with '0x00,0x00,0x00,0x01')
// fwrite(nalu_header, sizeof(uint8_t), 4, _file_ptr);
// fwrite(packet.m_body + offset, sizeof(uint8_t), data_len, _file_ptr);
// offset += data_len;
if(packet_.m_nBodySize >= 9) data_len = packet_.m_nBodySize - 9;
else return false;
std::shared_ptr<uint8_t[]> sp_buf(new uint8_t[data_len]);
memcpy(sp_buf.get(), packet_.m_body + offset, data_len);
call_back_hanlde_frame_(sp_buf.get(), data_len, packet_.m_nTimeStamp);
// AI_Log("RtmpImple", KLINFO, " get Nalu frame once, timestamp: {}", packet_.m_nTimeStamp);
}
}
RTMPPacket_Free(&packet_);
return true;
}
void RtmpImple::BindHandleFrame(std::function<void(uint8_t* data, uint64_t size, uint32_t pkt_ts)> call_back_hanlde_frame)
{
call_back_hanlde_frame_ = call_back_hanlde_frame;
}
void RtmpImple::BindHandleSPSPPS(std::function<void(uint8_t* data, uint64_t size, uint32_t width, uint32_t height)> call_back_hanlde_sps_pps)
{
call_back_hanlde_sps_pps_ = call_back_hanlde_sps_pps;
}
void RtmpImple::BindHandleCODEC(std::function<void(const std::string& codec)> call_back_hanlde_codec)
{
call_back_hanlde_codec_ = call_back_hanlde_codec;
}
最简单的win socket 示例:c.biancheng.net/cpp/html/30…
下载可执行的windows ffmpeg
Windows下下载安装ffmpeg - 知乎 (zhihu.com)
收藏的网页,我没时间看这些,先记下吧
-
avseek 相关
ffmpeg AVIOContext 自定义 IO 及 seek - SegmentFault 思否 -
avio_alloc_context内存处理相关
(76条消息) FFMPEG关于avio_alloc_context申请使用内存释放问题_路过&的博客-CSDN博客
(76条消息) ffmpeg之avio_alloc_context函数解读笔记_CrystalShaw的博客-CSDN博客_avio_alloc_context
(76条消息) ffmpeg 从内存中读取数据(或将数据输出到内存)_雷霄骅的博客-CSDN博客_ffmpeg读取内存数据 -
h264 裸流封装相关
ffmpeg h264文件和裸流 封装mp4 - 简书 (jianshu.com) - 文件流,网络流格式解析
(76条消息) ffmpeg学习(17)文件流、网络流的格式解析(使用AVIOContext )_wanggao_1990的博客-CSDN博客_文件流格式 -
ffmpeg 打开视频流太慢
ffmpeg 打开视频流太慢
(76条消息) FFmpeg源代码简单分析:avformat_find_stream_info()_雷霄骅的博客-CSDN博客_avformat_find_stream_info
(76条消息) FFMPEG avformat_find_stream_info替换_alpha2omega的博客-CSDN博客 -
visual studio 诊断内存
(76条消息) 使用vs诊断工具检测FFmpeg的内存泄漏问题_cuijiecheng2018的博客-CSDN博客_ffmpeg内存泄漏 -
ffmpeg # probesize & analyzeduration
ffmpeg # probesize & analyzeduration - 简书 (jianshu.com) -
avio_open2()
(76条消息) FFmpeg源代码简单分析:avio_open2()_雷霄骅的博客-CSDN博客_avio_open -
av_write_frame与av_interleaved_write_frame
(76条消息) av_write_frame与av_interleaved_write_frame_茄子船长的博客-CSDN博客_av_interleaved_write_frame -
FFmpeg、FFprobe、FFplay命令行使用
FFmpeg、FFprobe、FFplay命令行使用 - 简书 (jianshu.com) -
ffplay 的使用
FFmpeg命令行工具学习(二):播放媒体文件的工具ffplay - 灰色飘零 - 博客园 (cnblogs.com) -
rtsp over udp
(76条消息) RTSP over UDP与RTSP over TCP取流对比_luyumiao1990的博客-CSDN博客
(76条消息) Windra6的博客_CSDN博客-Android系统,音视频,linux内核领域博主
(76条消息) RTSP协议入门基础_Windra6的博客-CSDN博客_rtsp 教程
(76条消息) C++RTSP服务端(附源码)_keivin2006的博客-CSDN博客_c++ rtsp
(76条消息) <三:使用,接收端>live555--接收端 testRTSPClient.cpp 源码分析-读取rtsp流demo_王二の黄金时代的博客-CSDN博客 -
ffmpeg 添加字幕流FFMPEG系列之四:添加字幕流 | Spirit's Blog (spirithy.com)
mat 的内存布局,涉及到 step size elemSize
-
这个说的浅显易懂,注意看下评论
blog.csdn.net/qianqing135… -
这个说明内存布局是否连续,以及根据每维的
step[]转成指针来访问
blog.csdn.net/ChiKuo_Z/ar… -
这个也说的比较仔细: blog.csdn.net/guyuealian/…