Node服务端使用ffmpeg

2,129 阅读7分钟

背景

Node 使用 node-fluent-ffmpeg 进行 ffmpeg 操作,需要下载并且设置 ffmpeg 路径。node-ffmpeg-installer会自动下载ffmpeg,但是这个下载的ffmpeg 版本较老。ffmpeg-static,此版本为ffmpeg 5.x以上。只支持macOS(64位和arm64)、Linux(32位和64位、armhf、arm64)和Windows(32和64位)。

ffmpeg 各版本下载地址淘宝rpm地址ffmpeg 能不能低 CPU 转码视频

fluent-ffmpeg

ffmpeg的原生命令行较为复杂,而 fluent-ffmpeg 则将这些命令抽象为一个npm包,间接调用ffmpeg。 node-fluent-ffmpeg命令查看,一般配合 node-ffmpeg-installerffmpeg-static使用。也可以自主安装ffmpeg,将使用路径指向ffmpeg。

centos linux下安装ffmpeg

下载解压

wget http://www.ffmpeg.org/releases/ffmpeg-5.1.2.tar.gz
tar -zxvf ffmpeg-5.1.2.tar.gz 

进入解压后目录,输入如下命令/usr/local/ffmpeg为自己指定的安装目录

cd ffmpeg-5.1.2
./configure --prefix=/usr/local/ffmpeg
make && make install

配置变量

vi /etc/profile
在最后PATH添加环境变量:
export PATH=$PATH:/usr/local/ffmpeg/bin
保存退出
查看是否生效
source /ect/profile  设置生效

查看版本

ffmpeg -version    查看版本

配置参数编译

执行以下命令是最基础的编译,很多的ffmpeg参数无法使用,因此需要下载插件并且带上相应的参数进行编译。

./configure --prefix=/usr/local/ffmpeg

从如下图看到,安装的 ffmpeg 5.1.2 版本带了很多编译参数。

image.png

错误QA

1、yasm/nasm not found or too old

 yasm/nasm not found or too old. Use –disable-yasm for a crippled build.
 If you think configure made a mistake, make sure you are using the latest
 version from Git. If the latest version fails, report the problem to the
 ffmpeg-user@ffmpeg.org mailing list or IRC #ffmpeg on irc.freenode.net.
 Include the log file “config.log” produced by configure as this will help
 solve the problem.

需要安装 yasm

wget http://www.tortall.net/projects/yasm/releases/yasm-1.3.0.tar.gz
tar -zxvf yasm-1.3.0.tar.gz
cd yasm-1.3.0
./configure
make && make install

或者安装

yum install nasm -y

2、ffmpeg: error while loading shared libraries: libavdevice.so.58: cannot open shared object file: No such file or directory

export LD_LIBRARY_PATH=/usr/local/lib/

lbavdevice.so.58到底是什么东西呢?

lbavdevice是FFmpeg的一个库libavdevice库提供了用于从许多常见的多媒体输入/输出设备获取和呈现的通用框架,并支持多种输入和输出设备,包括Video4Linux2,VfW,DShow和ALSA。 .so 文件是基于Linux下的动态链接,其功能和作用类似与windows下.dll文件。

3、ffmpeg: error while loading shared libraries: libasound.so.2: cannot open shared object file: No such file or directory

yum -y install alsa-lib-devel

error while loading shared libraries: libasound.so.2: cannot open shared object file: No such file or directory

4、zsh: no matches found: rtmp:

setopt nonomatch
echo does-not-exist?
does-not-exist?

构建 RPM 包

centos 采用ffmpeg转码rtmp推流

错误QA

1、Failed to update header with correct duration

把ffmpeg -re -i改成ffmpeg -re -stream_loop -1 -i。让视频文件推流的时候无限循环(-stream_loop后面的数字为循环次数,写-1为无限循环)

1、av_interleaved_write_frame推流失败,AVPacket包使用注意

  1. 如果解码、二次处理、编码分配的AVPacket包,有引用计数器逻辑处理,av_interleaved_write_frame使用完AVPacket包,不能手动释放会导致程序奔溃;
  2. 建议自己分配好固定内存,进行解码、编码处理以及write_frame处理完后,调用ffmpeg提供相关处理函数就可以,中间处理期间不用考虑内存使用问题

2、av_interleaved_write_frame推流几分钟偶尔出现断流问题

  1. 按照当前推流情况出现以上问题是因为av_interleaved_write_frame不支持线程安全,音视频两线程同时调用该接口会导致数据错乱,最终导致rtmp推流失败异常断开
  2. 如果音视频pts计算不正确也会导致以上问题发生

3、音频重采用播放重叠问题

  1. 输入音频opus、48000、双声道,输出音频aac、44100、双声道,需要对音频进行重采样处理
  2. 音频解码、重采样、编码需要根据输入音频的PTS、音频编码后的数据量重新计算编码后的音频PTS,来解决音频播放堆叠问题;如下步骤:
    1.将输入opus流的pts进行转化为编码pts采用av_rescale_q_rnd()接口进行转化
    2.将编码当前pts加上采用变成一帧数据尾的pts,然后转化为微妙标记为mypts
    3.通过swr_convert重采样、fifo进行数据缓存,调用一下av_audio_fifo_size判断一下FIFO中的音频够不够frame_size,如果不够的话,那就要再进行前面的读取,如果够的话,那就调用av_audio_fifo_read从FIFO中读取frame_size个长度的音频数据,当然因为读取frame_size后,FIFO中可能还有数据,因此要计算一下FIFO中音频数据的微秒长度,标记为restpts
    4.重采样、编码后的aac编码pts=mypts-restpts
    5.通过av_rescale_q_rnd()接口将aac编码pts转化为aac流pts,该pts为推rtmp流的实际pts
  1. 以上计算涉及了ffmpeg内部时间基(timebase)的计算:
    1.ffmpeg 内部针对timebase的分层结构:
        1.转码流程(如:flv格式数据-->h264/aac数据-->yuv/pcm数据-->h264/aac数据-->mp4格式数据):
            1.把flv格式数据或者mp4格式数据这一层叫做mux/demux层或者复用层有些人习惯于叫做封装层;即:mux/demux层
            2.把h264/aac数据这一层叫做编解码层或者codec/decode;即:codec/decode层
            3.把yuv/pcm数据这一层叫做原始数据层或者Raw data层;即:Raw data层
        2.通过转码流程可以总结出ffmpeg基本把数据或者说结构分为了“mux/demux层”也就是ffmpeg中的AVStream:“codec/decode层” 也就是ffmpeg中的AVCodec ;“Raw data层”这个也在AVStream 中存放着(如果是自己填写的例如ios或者android获取当前毫秒时间的可以单独放置到一个timebse的结构体中)
        3.针对ffmpeg关于分层结构timebase的转换过程,转码流程里时间基转换过程:
            1.针对ffmpeg关于分层结构timebase的转换过程,转码流程里时间基转换过程:
                1.picture->pts = av_rescale_q_rnd(picture->pts,streams->time_base,streams->decodectext->time_base, AV_ROUND_NEAR_INF);
            2.然后将decode层的timebase转换成codec层(这两个同层但也需要转换)的timebase,这里有个问题Raw data 层的timebase可以略过,可以直接从decode转换为coedec,Raw data 层的timebse在摄像头采集和播放器的时候会用到。
                1.picture->pts = av_rescale_q_rnd(picture->pts,streams->decodectext->time_base,streams->codectext->time_base, AV_ROUND_NEAR_INF);
            3.最后将codec层的timebase转换为mux层的timebase。
                1.picture->pts = av_rescale_q_rnd(picture->pts,streams->codectext->time_base,streams->time_base,AV_ROUND_NEAR_INF)    
    2.具体可参考:https://blog.csdn.net/zhuweigangzwg/article/details/64919706 
  1. 音频重采样过程pts计算,实际跟ffmpeg转码过程内部pts计算流出一样

4、音频推流音频不正常问题

  1. 检测音频pcm数据是否正常
  2. 检测音频编码后的数据是否正常
  3. 以上都正常,如果推rtmp流,请通过av_bitstream_filter_init("aac_adtstoasc");该接口进行去掉aac的adts头,如下:
    1.将AAC编码器编码后的原始码流(ADTS头 + ES流)封装为MP4或者FLV或者MOV等格式时,需要先将ADTS头转换为MPEG-4 AudioSpecficConfig (将音频相关编解码参数提取出来),并将原始码流中的ADTS头去掉(只剩下ES流)
    2.相反,从MP4或者FLV或者MOV等格式文件中解封装出AAC码流(只有ES流)时,需要在解析出的AAC码流前添加ADTS头(含音频相关编解码参数)

5、ffmpeg通过api进行rtmp推流需要注意音视频首包问题

  1. rtmp推流在avformat_write_header时,会向rtmp服务器发送sps/pps、aac header数据,在编码音视频时需要在编码中使能CODEC_FLAG_GLOBAL_HEADER,但调用avformat_write_header前请确保音视频全局头流包含这些数据pVCodeCtx->extradata
  2. 如果使能CODEC_FLAG_GLOBAL_HEADER该标志,x264每一个I帧不会携带sps/pps如果需要请手动设置添加
  3. 在rtc场景下,分辨率会根据网络情况进行动态调整分辨率,如刚开始视频源是个1280720,但是数据源作为数据发送方通过网络带宽统计,得知当前网络丢包严重,画面需要降质,分辨率调整为640480,接收过程中识别到视频源编码信息发生了变化,需要把编码信息变化实时更新到flv容器中,在运行期间通过AVPacket更新avc sequence header的方法,一直以为这个信息是avcodec范畴修改才能才能更新的,曾经尝试更新AVCodecContext,也没有太多处理,最终通过av_packet_new_side_data函数来增加编码信息,这个函数自动给AVPacket分配了side_data的空间,而av_packet_free_side_data函数则可以释放分配的空间

6、ffmpeg推rtmp流,长时间推流偶尔会出现RTMP_Write阻塞,在avio_open2接口增加超时回调函数,来解决阻塞问题

int PushMediaStream::CallBackIOInterrupt(void *param) 
{
    if(param)
    {
       PushMediaStream *process - (PushMediaStream *)param;
       long long t -av_gettime()/1000 - process->llLasttime;
       int nDuration - 2000; // 单位毫秒
       if(t > nDuration)
       {
           ELOG_DEBUG("Interrupt write stream block");
           return 1;
       }
    }
    return 0;
}

参考

FFmpeg问题梳理