Raspberry pi 4b , 基于ffmpeg , 读取rtsp视频流,硬编解码视频流,加水印, 推送RTSP .
OS 💻
raspberry pi 4b 8g armv7l GNU/Linux
搭建 FFmpeg(4.1.1) 环境 ⭐️
-
⭐️ 安装 x264
-
安装
./configure --disable-shared --enable-static --enable-strip --disable-cli ./configure --enable-shared --enable-static --enable-strip --disable-cli --prefix=/usr/local/x264 make -j4 & sudo make install
-
⚡️ 由于需要叠加水印,所以需要安装 freetype、libxml2、fontconfig 、fribidi
-
安装 freetype (2.10.0) https://download.savannah.gnu.org/releases/freetype/
./configure --prefix=/usr/local/freetype make & sudo make install
-
安装 libxml2 (2.9.9) https://github.com/GNOME/libxml2/tags
./configure --without-python --prefix=/usr/local/libxml2 make -j4 & sudo make install
-
安装 fontconfig (2.13.92) https://www.freedesktop.org/software/fontconfig/release/
autoreconf -iv export PKG_CONFIG_PATH=/usr/local/libxml2/lib/pkgconfig:$PKG_CONFIG_PATH export PKG_CONFIG_PATH=/usr/local/freetype/lib/pkgconfig:$PKG_CONFIG_PATH ./configure --enable-libxml2 --with-freetype-config=/usr/local/freetype/bin/freetype-config --prefix=/usr/local/fontconfig make & sudo make install
-
安装 fribidi https://github.com/fribidi/fribidi
1. ./autogen.sh 2. ./configure --prefix=/usr/local/fribidi 3. make & sudo make install
-
-
🌞 安装 ffmpeg
-
设置环境变量
export PKG_CONFIG_PATH=/usr/local/freetype/lib/pkgconfig:$PKG_CONFIG_PATH export PKG_CONFIG_PATH=/usr/local/libxml2/lib/pkgconfig:$PKG_CONFIG_PATH export PKG_CONFIG_PATH=/usr/local/fontconfig/lib/pkgconfig:$PKG_CONFIG_PATH export PKG_CONFIG_PATH=/usr/local/fribidi/lib/pkgconfig:$PKG_CONFIG_PATH export PKG_CONFIG_PATH=/usr/local/x264/lib/pkgconfig:$PKG_CONFIG_PATH
-
raspberry 支持硬编解码,所以增加 h264_omx h264_mmal .
./configure \ --enable-gpl --enable-version3 --enable-nonfree \ --enable-static --enable-shared \ \ --prefix=/usr/local/ffmpeg \ \ --disable-opencl \ --disable-thumb \ --disable-pic \ --disable-stripping \ \ --enable-small \ \ --enable-ffmpeg \ --enable-ffplay \ --enable-ffprobe \ \ --disable-doc \ --disable-htmlpages \ --disable-podpages \ --disable-txtpages \ --disable-manpages \ \ --disable-everything \ \ --enable-libx264 \ --enable-encoder=libx264 \ --enable-decoder=h264 \ --enable-encoder=aac \ --enable-decoder=aac \ --enable-encoder=ac3 \ --enable-decoder=ac3 \ --enable-encoder=rawvideo \ --enable-decoder=rawvideo \ --enable-encoder=mjpeg \ --enable-decoder=mjpeg \ \ --enable-demuxer=concat \ --enable-muxer=flv \ --enable-demuxer=flv \ --enable-demuxer=live_flv \ --enable-muxer=hls \ --enable-muxer=segment \ --enable-muxer=stream_segment \ --enable-muxer=mov \ --enable-demuxer=mov \ --enable-muxer=mp4 \ --enable-muxer=mpegts \ --enable-demuxer=mpegts \ --enable-demuxer=mpegvideo \ --enable-muxer=matroska \ --enable-demuxer=matroska \ --enable-muxer=wav \ --enable-demuxer=wav \ --enable-muxer=pcm* \ --enable-demuxer=pcm* \ --enable-muxer=rawvideo \ --enable-demuxer=rawvideo \ --enable-muxer=rtsp \ --enable-demuxer=rtsp \ --enable-muxer=rtsp \ --enable-demuxer=sdp \ --enable-muxer=fifo \ --enable-muxer=tee \ \ --enable-parser=h264 \ --enable-parser=aac \ \ --enable-protocol=file \ --enable-protocol=tcp \ --enable-protocol=rtmp \ --enable-protocol=cache \ --enable-protocol=pipe \ \ --enable-filter=aresample \ --enable-filter=allyuv \ --enable-filter=scale \ --enable-filter=drawtext \ --enable-libfreetype \ \ --enable-indev=v4l2 \ --enable-indev=alsa \ \ --enable-omx \ --enable-omx-rpi \ --enable-encoder=h264_omx \ \ --enable-mmal \ --enable-hwaccel=h264_mmal \ --enable-decoder=h264_mmal \ \ --enable-libfreetype \ --enable-libfontconfig \ --enable-libfribidi \ \ --enable-encoder=pcm_alaw \ --enable-decoder=pcm_alaw make -j & sudo make install
-
测试
./ffmpeg -c:v hx264 -i 2.mp4 -c:v h264_omx -c:a copy -b:v 1500k out2.mp4 ./ffmpeg -i 2.mp4 -vf "drawtext=fontfile=simsun.ttc: text='aaaa':x=10:y=10:fontsize=24:fontcolor=white:shadowy=2" output.mp4
-
搭建RTSP 服务器 🤠
- RTSP 服务器: https://github.com/EasyDarwin/EasyDarwin/releases
- RTSP 播放器: https://github.com/tsingsee/EasyPlayer-RTSP-Win
代码实现 🎃
- 连接 rtsp
int AVFFmpeg::connect(const char* rtsp){
int ret;
av_dict_set(&pOptionsDict_, "buffer_size", "409600", 0);
av_dict_set(&pOptionsDict_, "rtsp_transport", "tcp", 0);
av_dict_set(&pOptionsDict_, "stimeout", "1000000", 0);
av_dict_set(&pOptionsDict_, "max_delay", "500000", 0);
if ((ret = avformat_open_input(&ifmtCtx_, rtsp, 0, pOptionsDict_ == nullptr ? 0 : &pOptionsDict_)) != 0)
{
releaseObj();
return -1;
}
if ((ret = avformat_find_stream_info(ifmtCtx_, NULL)) < 0)
{
releaseObj();
return -1;
}
// dump information
for (unsigned int i = 0; i < ifmtCtx_->nb_streams; i++)
av_dump_format(ifmtCtx_, i, rtsp, 0);
findAVStreamIndex(ifmtCtx_, vStreamIndex_, aStreamIndex_);
if (vStreamIndex_ == -1) {
releaseObj();
return -1;
}
// decode ctx
AVStream* pVst = ifmtCtx_->streams[vStreamIndex_];
pVideoDeCodecCtx_ = createVideoDecoderCtx(pVst);
if (pVideoDeCodecCtx_ == nullptr)
{
releaseObj();
return -1;
}
// encode ctx
pVideoEnCodecCtx_ = createVideoEncoderCtx(pVst);
if (pVideoEnCodecCtx_ == nullptr)
{
releaseObj();
return -1;
}
isConnected_ = true;
return 0;
}
- 创建解码器, 使用硬解码
h264_mmal
AVCodecContext* AVFFmpeg::createVideoDecoderCtx(AVStream* pVst) {
AVCodec* videoCodec = avcodec_find_decoder_by_name("h264_mmal");
if (videoCodec == nullptr)
return nullptr;
AVCodecContext* codecCtx = avcodec_alloc_context3(videoCodec);
if (codecCtx == nullptr)
return nullptr;
avcodec_parameters_to_context(codecCtx, pVst->codecpar);
codecCtx->time_base = {pVst->codec->time_base.num , pVst->codec->time_base.den };
if (avcodec_open2(codecCtx, videoCodec, NULL) < 0)
{
avcodec_free_context(&codecCtx);
return nullptr;
}
return codecCtx;
}
- 创建编码器 ,使用硬编码
h264_omx
AVCodecContext* AVFFmpeg::createVideoEncoderCtx(AVStream* pVst) {
AVCodec* codec = avcodec_find_encoder_by_name("h264_omx");
if (codec == nullptr)
return nullptr;
AVCodecContext* codecCtx = avcodec_alloc_context3(codec);
if (codecCtx == nullptr)
return nullptr;
codecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
codecCtx->width = pVst->codecpar->width;
codecCtx->height = pVst->codecpar->height;
codecCtx->bit_rate = 1024000;
codecCtx->gop_size = 50;
codecCtx->framerate = { pVst->codec->framerate.num, pVst->codec->framerate.den };
codecCtx->time_base = { pVst->codec->time_base.num, pVst->codec->time_base.den };
codecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
av_opt_set(codecCtx->priv_data, "tune", "zerolatency", 0);
if (avcodec_open2(codecCtx, codec, NULL) < 0)
{
avcodec_free_context(&codecCtx);
return nullptr;
}
return codecCtx;
}
- 开启推送rtsp , 推流rtsp地址格式: rtp://ip:port/xx/xx. ip: rtsp 服务器Ip, 端口默认 554。( 如果推送rtmp, 则 “rtsp” -> “flv”)
int AVFFmpeg::startPushStream(const char* url) {
if (ofmtCtx_ != nullptr)
return -1;
int ret;
// rtmp "rtsp" -> "flv"
ret = avformat_alloc_output_context2(&ofmtCtx_, NULL, "rtsp", url);
if (ret < 0)
return -1;
// tcp
av_opt_set(ofmtCtx_->priv_data, "rtsp_transport", "tcp", 0);
// check if has data ,no data wait max_interleave_delta us.
ofmtCtx_->max_interleave_delta = 1000000; // us
ofmtCtx_->max_delay = 100000;
ret = createPushStream(ifmtCtx_);
if (ret < 0) {
avformat_free_context(ofmtCtx_);
return -1;
}
av_dump_format(ofmtCtx_, 0, url, 1);
if (!(ofmtCtx_->flags & AVFMT_NOFILE)) {
ret = avio_open(&ofmtCtx_->pb, url, AVIO_FLAG_WRITE);
if (ret < 0) {
avformat_free_context(ofmtCtx_);
return -1;
}
}
ofmtCtx_->video_codec_id = ofmtCtx_->oformat->video_codec;
if(avformat_write_header(ofmtCtx_, NULL) < 0){
avformat_free_context(ofmtCtx_);
return -1;
}
mStartPushStream_ = true;
return 0;
}
- 加水印文字,目前设置四个角
AVFrame* AVOSD::setOSDInfo(AVFrame* frame, int width, int height, OSDInfo& info) {
AVFilterContext* buffersink_ctx;
AVFilterContext* buffersrc_ctx;
// osd
AVFilterGraph* filter_graph = get_filter_graph(frame, width, height, &buffersink_ctx, &buffersrc_ctx, info);
if (filter_graph == nullptr)
return nullptr;
int ret = av_buffersrc_add_frame(buffersrc_ctx, frame);
if (ret < 0)
{
avfilter_graph_free(&filter_graph);
filter_graph = NULL;
return nullptr;
}
AVFrame* out = av_frame_alloc();
ret = av_buffersink_get_frame(buffersink_ctx, out);
avfilter_graph_free(&filter_graph);
filter_graph = NULL;
if (ret < 0)
{
av_frame_free(&out);
return nullptr;
}
return out;
}
- 实现预览推送
int AVFFmpeg::startRealPlay(){
if(!isConnected_)
return -1;
int ret;
mStartRealPlay_ = true;
bool flag = false;
AVPacket pkt;
av_init_packet(&pkt);
while (mStartRealPlay_)
{
ret = av_read_frame(ifmtCtx_, &pkt);
if (ret < 0)
{ // disconnect
av_packet_unref(&pkt);
releaseObj();
break;
}
// audio
if (pkt.stream_index == aStreamIndex_) {
av_packet_unref(&pkt);
continue;
}
// decode packet to frame
AVFrame* in = decodePacketToFrame(pVideoDeCodecCtx_, pkt, ret);
av_packet_unref(&pkt);
if (ret != 0)
continue;
int srcW = pVideoDeCodecCtx_->width;
int srcH = pVideoDeCodecCtx_->height;
// add osd info to frame
AVFrame* out = pOSD_->setOSDInfo(in, srcW, srcH, osdInfo_);
av_frame_free(&in);
if (out == nullptr)
continue;
// push stream
if(mStartPushStream_){
AVPacket* epkt= av_packet_alloc();
int ret = avcodec_send_frame(pVideoEnCodecCtx_, out);
if (ret == 0){
while(avcodec_receive_packet(pVideoEnCodecCtx_, epkt)>=0){
ret = writePushStream(epkt);
}
}
av_packet_free(&epkt);
}
av_frame_free(&out);
}
stopPushStream();
isStopPreview_ = true;
return 0;
}
- main
int main(){
pthread_t tids[1];
const char* rtspUrl = "rtsp://xxxxxxxx";
const char* pushUrl = "rtp://user:pwd@ip:port/xxxxx";
int ret = avFm_.connect(rtspUrl);
if(ret != 0)
{
std::cout << "rtsp connect failed..." << std::endl;
return -1;
}
ret = pthread_create(&tids[0], NULL, connect_rtsp, NULL);
if (ret != 0)
{
avFm_.disconnect();
std::cout << "pthread_create error: error_code=" << ret << std::endl;
return -1;
}
usleep(5000000);
std::cout << "start push : " << pushUrl << std::endl;
ret = avFm_.startPushStream(pushUrl);
if(ret != 0)
{
std::cout << "startPushStream failed..." << std::endl;
return -1;
}
usleep(30000000);
std::cout << "stop push.... " << std::endl;
avFm_.stopPushStream();
usleep(1000000);
std::cout << "stop real play .... " << std::endl;
avFm_.stopRealPlay();
usleep(1000000);
std::cout << "disconnect .... " << std::endl;
avFm_.disconnect();
std::cout << "finish .... " << std::endl;
return 0;
}
- 以上是部分实现代码,有需要的小伙伴可以参考参考. 完整代码: https://download.csdn.net/download/haiyangyunbao813/85149687
Q&A 👨🚀
-
ERROR: OMX_Core.h not found
sudo apt-get install libomxil-bellagio-dev
-
avcodec_open2 阻塞不动 , mmal error 2 on control port , OMX error 80001000
修改 /boot/config.txt [all] #dtoverlay=vc4-fkms-v3d gpu_mem=512 --- sudo reboot
Demo: ✨
参考文章 📖
- https://www.jianshu.com/p/dec9bf9cffc9
- https://blog.csdn.net/Tang_Chuanlin/article/details/85244429
- https://www.willusher.io/general/2020/11/15/hw-accel-encoding-rpi4
- https://its401.com/article/wanggao_1990/116195910
- https://qa.icopy.site/questions/60529213/how-to-enable-hardware-support-for-h-264-encoding-on-raspberry-pi-4b
END🔚
-
以上基本是我整理得全部内容了,有不对得地方,欢迎大家留言指正,感谢。 🤔
-
码字不易,纯手工输出,感觉有一丢丢有用,麻烦给个好评呗。 😂