本文将介绍 FFmpeg 如何播放 RTSP/Webcam/File 流。流程如下:

RTSP/Webcam/File > FFmpeg open and decode to BGR/YUV > OpenCV/OpenGL display

FFmpeg 准备

git clone https://github.com/ikuokuo/rtsp-wasm-player.git
cd rtsp-wasm-player
export MY_ROOT=`pwd` # ffmpeg: https://ffmpeg.org/
git clone --depth 1 -b n4.4 https://git.ffmpeg.org/ffmpeg.git $MY_ROOT/3rdparty/source/ffmpeg
cd $MY_ROOT/3rdparty/source/ffmpeg
./configure --prefix=$MY_ROOT/3rdparty/ffmpeg-4.4 \
--enable-gpl --enable-version3 \
--disable-programs --disable-doc --disable-everything \
--enable-decoder=h264 --enable-parser=h264 \
--enable-decoder=hevc --enable-parser=hevc \
--enable-hwaccel=h264_nvdec --enable-hwaccel=hevc_nvdec \
--enable-demuxer=rtsp \
--enable-demuxer=rawvideo --enable-decoder=rawvideo --enable-indev=v4l2 \
--enable-protocol=file
make -j`nproc`
make install
ln -s ffmpeg-4.4 $MY_ROOT/3rdparty/ffmpeg

./configure 手动选择了:解码 h264,hevc 、解封装 rtsp,rawvideo 、及协议 file ,以支持 RTSP/Webcam/File 流。

其中, Webcam 因于 Linux ,故用的 v4l2。 Windows 可用 dshow, macOS 可用 avfoundation ,详见 Capture/Webcam

这里依据自己需求进行选择,当然,也可以直接编译全部。

FFmpeg 拉流

拉流过程,主要涉及的模块:

  • avdevice: IO 设备支持(次要,为了 Webcam)
  • avformat: 打开流,解封装,拿小包(主要)
  • avcodec: 收包,解码,拿帧(主要)
  • swscale: 图像缩放,转码(次要)

解封装,拿包

完整代码,见 stream.cc

打开输入流:

// IO 设备注册 for Webcam
avdevice_register_all();
// Network 初始化 for RTSP
avformat_network_init(); // 打开输入流
format_ctx_ = avformat_alloc_context();
avformat_open_input(&format_ctx_, "rtsp://", nullptr, nullptr);

找出视频流:

avformat_find_stream_info(format_ctx_, nullptr);

video_stream_ = nullptr;

for (unsigned int i = 0; i < format_ctx_->nb_streams; i++) {
auto codec_type = format_ctx_->streams[i]->codecpar->codec_type;
if (codec_type == AVMEDIA_TYPE_VIDEO) {
video_stream_ = format_ctx_->streams[i];
break;
} else if (codec_type == AVMEDIA_TYPE_AUDIO) {
// ignore
}
}

循环拿包:

if (packet_ == nullptr) {
packet_ = av_packet_alloc();
}
av_read_frame(format_ctx_, packet_);
if (packet_->stream_index == video_stream_->GetIndex()) {
// 如果是视频流,处理其解码、拿帧等
}
av_packet_unref(packet_);

解码,拿帧

完整代码,见 stream_video.cc

解码初始化:

if (codec_ctx_ == nullptr) {
AVCodec *codec_ = avcodec_find_decoder(video_stream_->codecpar->codec_id); codec_ctx_ = avcodec_alloc_context3(codec_); avcodec_parameters_to_context(codec_ctx_, stream_->codecpar);
avcodec_open2(codec_ctx_, codec_, nullptr); frame_ = av_frame_alloc(); // 帧
}

解码收包,返帧:

int ret = avcodec_send_packet(codec_ctx_, packet);
if (ret != 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) {
throw StreamError(ret);
} ret = avcodec_receive_frame(codec_ctx_, frame_);
if (ret != 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) {
throw StreamError(ret);
} // frame_ is ok here

注意处理特别返回码: EAGAIN 表示要继续收包、EOF 表示结束,另外还有些特别码。

缩放,转码

// 初始化
if (sws_ctx_ == nullptr) {
// 设定目标大小及编码
auto pix_fmt = options_.sws_dst_pix_fmt;
int width = options_.sws_dst_width;
int height = options_.sws_dst_height;
int align = 1;
int flags = SWS_BICUBIC; sws_frame_ = av_frame_alloc(); int bytes_n = av_image_get_buffer_size(pix_fmt, width, height, align);
uint8_t *buffer = static_cast<uint8_t *>(
av_malloc(bytes_n * sizeof(uint8_t)));
av_image_fill_arrays(sws_frame_->data, sws_frame_->linesize, buffer,
pix_fmt, width, height, align); sws_frame_->width = width;
sws_frame_->height = height; // 实例化
sws_ctx_ = sws_getContext(
codec_ctx_->width, codec_ctx_->height, codec_ctx_->pix_fmt,
width, height, pix_fmt, flags, nullptr, nullptr, nullptr);
if (sws_ctx_ == nullptr) throw StreamError("Get sws context fail");
} // 缩放或转码
sws_scale(sws_ctx_, frame_->data, frame_->linesize, 0, codec_ctx_->height,
sws_frame_->data, sws_frame_->linesize); // sws_frame_ as the result frame

OpenCV 显示

完整代码,见 main_ui_with_opencv.cc

转码成 bgr24,用于显示:

cv::namedWindow("ui");

try {
Stream stream;
stream.Open(options); while (1) {
auto frame = stream.GetFrameVideo();
if (frame != nullptr) {
cv::Mat image(frame->height, frame->width, CV_8UC3,
frame->data[0], frame->linesize[0]);
cv::imshow(win_name, image);
}
char key = static_cast<char>(cv::waitKey(10));
if (key == 27 || key == 'q' || key == 'Q') { // ESC/Q
break;
}
} stream.Close();
} catch (const StreamError &err) {
LOG(ERROR) << err.what();
} cv::destroyAllWindows();

OpenGL 显示

完整代码,见 glfw_frame.h, main_ui_with_opengl.cc

转码成 yuyv420p 用于显示:

void OnDraw() override {
if (frame_ != nullptr) {
auto width = frame_->width;
auto height = frame_->height;
auto data = frame_->data[0]; auto len_y = width * height;
auto len_u = (width >> 1) * (height >> 1); // yuyv420p 可直接寻址三个平面的数据,赋值进纹理
texture_y_->Fill(width, height, data);
texture_u_->Fill(width >> 1, height >> 1, data + len_y);
texture_v_->Fill(width >> 1, height >> 1, data + len_y + len_u);
} glBindVertexArray(vao_);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}

片段着色器,直接转成 RGB

#version 330 core
in vec2 vTexCoord; uniform sampler2D yTex;
uniform sampler2D uTex;
uniform sampler2D vTex; // yuv420p to rgb888 matrix
const mat4 YUV2RGB = mat4(
1.1643828125, 0, 1.59602734375, -.87078515625,
1.1643828125, -.39176171875, -.81296875, .52959375,
1.1643828125, 2.017234375, 0, -1.081390625,
0, 0, 0, 1
); void main() {
gl_FragColor = vec4(
texture(yTex, vTexCoord).x,
texture(uTex, vTexCoord).x,
texture(vTex, vTexCoord).x,
1
) * YUV2RGB;
}

结语

本文代码想要编译运行的话,请依照 README 进行。

GoCoding 个人实践的经验分享,可关注公众号!

FFmpeg 播放 RTSP/Webcam 流的更多相关文章

  1. ffmpeg播放RTSP的一点优化

    简单记录一下最近使用ffmpeg播放RTSP做的一点参数优化. 先做如下定义: AVDictionary* options = NULL; 1.画质优化 原生的ffmpeg参数在对1920x1080的 ...

  2. 使HTML5支持RTSP流 微信直播RTSP流 微信播放RTSP直播流(HTML5播放rtsp,web播放rtsp,微信支持rtsp)

    一.大家都知道HTML5的VIDEO可以播放视频,但是H5不支持RTSP播放,所以需要中间件! 二.我们经理长年的努力,开发了HTML5支持RTSP的中间件,使HTML5支持RTSP直播! 三.不卡顿 ...

  3. FFmpeg开发笔记(九):ffmpeg解码rtsp流并使用SDL同步播放

    前言   ffmpeg播放rtsp网络流和摄像头流.   Demo   使用ffmpeg播放局域网rtsp1080p海康摄像头:延迟0.2s,存在马赛克     使用ffmpeg播放网络rtsp文件流 ...

  4. VLC播放RTSP视频延迟问题

    VLC播放RTSP视频延迟问题 配置 VLC 以播放 RTSP/RTP 流 实测发现RTP都不如TCP快? vlc播放rtp封装的h.264延时很大是什么原因? 开启打印: VLC的工具->消息 ...

  5. 【FFmpeg】ffplay播放rtsp视频流花屏问题

    问题描述:ffplay播放rtsp视频流时,播放过程中随机出现花屏现象. 基本流程学习:阅读ffplay源码,熟悉其播放rtsp视频流的基本流程. 在ffplay源码阅读和分析的基础上,画出了其播放r ...

  6. 基于Live555,ffmpeg的RTSP播放器直播与点播

    基于Live555,ffmpeg的RTSP播放器直播与点播 多路RTSP高清视频播放器下载地址:http://download.csdn.net/detail/u011352914/6604437多路 ...

  7. 搭建rtmp直播流服务之3:java开发ffmpeg实现rtsp转rtmp并实现ffmpeg命令的接口化管理架构设计及代码实现

    上一篇文章简单介绍了java如何调用ffmpeg的命令:http://blog.csdn.net/eguid_1/article/details/51777716 上上一篇介绍了nginx-rtmp服 ...

  8. 【FFmpeg】ffplay播放rtsp视频流花屏问题 (转)

    问题描述:ffplay播放rtsp视频流时,播放过程中随机出现花屏现象. 基本流程学习:阅读ffplay源码,熟悉其播放rtsp视频流的基本流程. 在ffplay源码阅读和分析的基础上,画出了其播放r ...

  9. ffmpeg接收rtsp流问题

    项目使用mingw环境g++5.3,C++调用ffmpeg接收rtsp流,再通过C#显示.结构上是C#调用C++的so文件,读取得到的视频帧(RGB888格式),通过图片控件显示. 一开始是使用ope ...

随机推荐

  1. Java 获取、删除Word文本框中的表格

    本文介绍如何来获取Word文本框中包含的表格,以及删除表格. 程序测试环境包括: IDEA JDK 1.8.0 Spire.Doc.jar 注:jar导入,可通过创建Maven程序项目,并在pom.x ...

  2. Debian9 无线网卡驱动安装

    https://wiki.debian.org/iwlwifi sudo vi /etc/apt/sources.list --------- ..... deb http://httpredir.d ...

  3. SpringBoot+Redis 实现消息订阅发布

    什么是 Redis Redis 是一个开源的使用 ANSI C语言编写的内存数据库,它以 key-value 键值对的形式存储数据,高性能,读取速度快,也提供了持久化存储机制. Redis 通常在项目 ...

  4. sql语句优化原理

    前言 网上有很多关于sql语句优化的文章,我这里想说下为什么这样...写sql语句,能够提高查询的效率. 1 sql语句优化原理 要想写出好的sql,就要学会用数据库的方式来思考如何执行sql,那么什 ...

  5. [考试总结]noip模拟20

    第五场,再挂分就没了.. 然后就没了.. 考场上一直想方法. 似乎想到了 \(T1\) 正解. 然而几个 \(k\) 的调试信息都让我迷失了自我. 然后有几句啥都没用的语句加在了上面.. 挂分... ...

  6. form 表单提交的另一种方式 js

    <html> <head> <script type="text/javascript"> function formSubmit() { fm ...

  7. FreeRTOS-03-其它任务相关函数

    说明: 本文仅作为学习FreeRTOS的记录文档,作为初学者肯定很多理解不对甚至错误的地方,望网友指正. FreeRTOS是一个RTOS(实时操作系统)系统,支持抢占式.合作式和时间片调度.适用于微处 ...

  8. 10分钟物联网设备接入阿里云IoT平台

    前言最近尝试了一下阿里云IoT物联网平台,还是蛮强大的.在此记录一下学习过程.本教程不需要任何外围硬件,一台电脑和一根能上网的网线即可.算是一篇Hello World了.先上效果图 第一章 准备工作1 ...

  9. wireshark常见分析

    转载于https://www.cnblogs.com/sn1per/p/12553064.html https://www.cnblogs.com/csnd/p/12332421.html 直接看上面 ...

  10. Zabbix中Agent自动注册

    目录 Active agent自动注册 以下情况,自动注册会自动运行: 配置 服务端配置 客户端配置 Active agent自动注册 zabbix Active agent可以实现自动注册,进而服务 ...