找人代做网站注意事项,开发者模式打开好还是关闭好,网站建站费用多少钱,wordpress嵌入外部网页实验平台#xff1a;Ubuntu20.04
摄像头#xff1a;普通USB摄像头#xff0c;输出格式为YUV422
1.配置RTMP服务器推流平台
使用Nginx 配置1935端口即可#xff0c;贴上教程地址
ubuntu20.04搭建Nginxrtmp服务器)
2.配置FFmpeg开发环境
过程较为简单#xff0c;这里不…实验平台Ubuntu20.04
摄像头普通USB摄像头输出格式为YUV422
1.配置RTMP服务器推流平台
使用Nginx 配置1935端口即可贴上教程地址
ubuntu20.04搭建Nginxrtmp服务器)
2.配置FFmpeg开发环境
过程较为简单这里不再赘述可以看博主的往期博客贴上教程地址配置FFmpeg开发环境 VscodeCMake
3.推流具体实现流程
总体流程图 3.1 设备初始化
有些摄像头可能支持输出多种参数因此一定要检查摄像头支持的格式
v4l2-ctl: 一个命令行工具用于控制和调试V4L2设备。可以查询设备信息、设置参数、捕获视频帧等。
v4l2-ctl --list-formats-ext # 列出设备支持的所有格式
v4l2-ctl --set-fmt-videowidth1920,height1080,pixelformatH264 # 设置视频格式
v4l2-ctl --stream-mmap --stream-count100 --stream-tooutput.raw # 捕获视频流查看本次实验的摄像头的相关参数
marxistubuntu:~/Desktop/audio_test/build$ v4l2-ctl --list-formats-ext
ioctl: VIDIOC_ENUM_FMTType: Video Capture[0]: MJPG (Motion-JPEG, compressed)Size: Discrete 1920x1080Interval: Discrete 0.033s (30.000 fps)Size: Discrete 640x480Interval: Discrete 0.008s (120.101 fps)Interval: Discrete 0.011s (90.000 fps)Interval: Discrete 0.017s (60.500 fps)Interval: Discrete 0.033s (30.200 fps)Size: Discrete 1280x720Interval: Discrete 0.017s (60.000 fps)Interval: Discrete 0.033s (30.500 fps)Size: Discrete 1024x768Interval: Discrete 0.033s (30.000 fps)Size: Discrete 800x600Interval: Discrete 0.017s (60.000 fps)Size: Discrete 1280x1024Interval: Discrete 0.033s (30.000 fps)Size: Discrete 320x240Interval: Discrete 0.008s (120.101 fps)[1]: YUYV (YUYV 4:2:2)Size: Discrete 1920x1080Interval: Discrete 0.167s (6.000 fps)Size: Discrete 640x480Interval: Discrete 0.033s (30.000 fps)Size: Discrete 1280x720Interval: Discrete 0.111s (9.000 fps)Size: Discrete 1024x768Interval: Discrete 0.167s (6.000 fps)Size: Discrete 800x600Interval: Discrete 0.050s (20.000 fps)Size: Discrete 1280x1024Interval: Discrete 0.167s (6.000 fps)Size: Discrete 320x240Interval: Discrete 0.033s (30.000 fps)
由上述可知摄像头一共支持两种格式一是MJPG格式已经由硬件压缩好的一种格式一种就是常见的YUV422格式YUV同样支持多种分辨率格式。
设置输入格式上下文Linux系统对应的是V4L2查找视频流信息
AVInputFormat *input_format av_find_input_format(v4l2);if ((ret avformat_open_input(input_ctx, /dev/video0, input_format, options)) 0){fprintf(stderr, Could not open input\n);return ret;}// 查找流信息ret avformat_find_stream_info(input_ctx, NULL);if (ret 0){std::cerr could not find stream info std::endl;return -1;}// 查找视频流for (size_t i 0; i input_ctx-nb_streams; i){if (input_ctx-streams[i]-codecpar-codec_type AVMEDIA_TYPE_VIDEO){video_stream_index i;break;}}if (video_stream_index -1){std::cerr no video stream found std::endl;return -1;}3.2 初始化编码器
本次推流实验使用的是H264编码器CPU软编码没有使用到硬件编码,用到的库是X264。
主要流程为 查找编码器——分配编码器上下文——设置编码器参数——打开编码器 // 查找编码器AVCodec *codec avcodec_find_encoder(AV_CODEC_ID_H264);if (!codec){fprintf(stderr, Could not find AV_CODEC_ID_H264\n);return -1;}// 分配编码器上下文AVCodecContext *codec_ctx avcodec_alloc_context3(codec);if (!codec_ctx){std::cerr Could not allocate video codec context std::endl;return -1;}// 设置编码器参数codec_ctx-codec_id codec-id;codec_ctx-bit_rate 400000;codec_ctx-width 1280;codec_ctx-height 720;codec_ctx-time_base (AVRational){1, 9};codec_ctx-framerate (AVRational){9, 1};codec_ctx-gop_size 10;codec_ctx-max_b_frames 0; // 不需要B帧codec_ctx-pix_fmt AV_PIX_FMT_YUV420P; // 传入的420P 格式而cam 默认输出422 则一会 需要作转换// 打开编码器if (avcodec_open2(codec_ctx, codec, NULL) 0){fprintf(stderr, Could not open codec\n);return -1;}这里将B帧参数设置为了0因为加入B帧之后虽然提高了压缩效率但是也显著增加了编码的复杂性。编码B帧需要更多的计算资源因为它不仅需要前向预测还需要后向预测。对于资源受限的设备如移动设备、嵌入式系统等不使用B帧可以减少编码器的负担。
3.3 设置输出流
这里的输出流地址则特指的RTMP服务器地址也就是说FFmpeg将编码好的数据传输到RTMP服务器。如果需要写入到文件输出流地址也可以是文件路径。
相关代码操作 // 创建输出流AVStream *out_stream avformat_new_stream(output_ctx, codec);if (!out_stream){fprintf(stderr, Could not avformat_new_stream\n);return -1;}// 从输入流复制参数到输出流avcodec_parameters_from_context(out_stream-codecpar, codec_ctx);out_stream-time_base codec_ctx-time_base;// 打开输出URLif (!(output_ctx-oformat-flags AVFMT_NOFILE)){if (avio_open(output_ctx-pb, output_url, AVIO_FLAG_WRITE) 0){std::cerr Could not open output URL std::endl;return -1;}}// 写输出文件头if (avformat_write_header(output_ctx, NULL) 0){fprintf(stderr, Could not write header\n);return -1;}3.4 读取摄像头数据
av_read_frame(input_ctx, pkt)
代码作用是从输入设备读取数据帧封装到packet中。
根据上文已经获取到了视频流索引在此判断一下是不是视频流因为有些摄像头支持语音输入packet中存放的也可能是音频流
pkt.stream_index video_stream_index
3.5 颜色空间转换
在上述过程中 已经指定输出YUV422的数据了因此需要转换为YUV420数据
大体流程为原始帧—转换上下文—YUV420帧
首先初始化转换上下文 // 准备颜色空间色彩转换SwsContext *sws_ctx sws_getContext(codec_ctx-width, codec_ctx-height, AV_PIX_FMT_YUYV422,codec_ctx-width, codec_ctx-height, AV_PIX_FMT_YUV420P,SWS_BILINEAR, NULL, NULL, NULL);if (!sws_ctx){std::cerr Could not initialize the conversion context std::endl;return -1;}
分辨率与编码器参数保持一致
准备原始数据帧从摄像头读取的数据包中得到
AVFrame *temp_frame av_frame_alloc();if (!temp_frame){std::cerr Could not allocate temporary frame std::endl;av_packet_unref(pkt);continue;}// 分配临时帧的内存空间if (av_image_alloc(temp_frame-data, temp_frame-linesize, codec_ctx-width, codec_ctx-height, AV_PIX_FMT_YUYV422, 1) 0){std::cerr Could not allocate temporary frame buffer std::endl;av_frame_free(temp_frame);av_packet_unref(pkt);continue;}// 将pkt.data中的数据填充到temp_frameret av_image_fill_arrays(temp_frame-data, temp_frame-linesize, pkt.data, AV_PIX_FMT_YUYV422, codec_ctx-width, codec_ctx-height, 1);if (ret 0){std::cerr Error filling arrays std::endl;av_freep(temp_frame-data[0]);av_frame_free(temp_frame);av_packet_unref(pkt);continue;}初始化YUV420的帧 // 分配AVFrame并设置参数AVFrame *frame av_frame_alloc();if (!frame){std::cerr Could not allocate video frame std::endl;return -1;}frame-format codec_ctx-pix_fmt;frame-width codec_ctx-width;frame-height codec_ctx-height;av_frame_get_buffer(frame, 32);最后执行转换即可
sws_scale(sws_ctx, temp_frame-data, temp_frame-linesize, 0, codec_ctx-height, frame-data, frame-linesize);
3.6 编码并输出到RTMP服务器
得到YUV420的数据就可以进行最后的操作了 // 编码视频数据ret avcodec_send_frame(codec_ctx, frame);if (ret 0){std::cerr Error sending frame to encoder std::endl;break;}while (ret 0){ret avcodec_receive_packet(codec_ctx, pkt);if (ret AVERROR(EAGAIN) || ret AVERROR_EOF){break;}else if (ret 0){std::cerr Error encoding frame std::endl;break;}// 将编码后的视频数据推送到RTMP服务器pkt.stream_index out_stream-index;av_packet_rescale_ts(pkt, codec_ctx-time_base, out_stream-time_base);pkt.pos -1;ret av_interleaved_write_frame(output_ctx, pkt);if (ret 0){std::cerr Error writing frame std::endl;break;}av_packet_unref(pkt);}4.完整代码
extern C
{
#include libavformat/avformat.h
#include libavdevice/avdevice.h
#include libavutil/opt.h
#include libavcodec/avcodec.h
#include libswscale/swscale.h
#include libavutil/imgutils.h
}
#include iostream
#include cstdlib
using namespace std;int main(int argc, char *argv[])
{const char *output_url rtmp://192.168.1.79:1935/orin/live; // 替换为你的RTMP推流地址AVFormatContext *input_ctx NULL;AVPacket pkt;int ret;int video_stream_index -1;AVDictionary *options nullptr; // 摄像头相关参数int64_t pts 0; // 初始化 PTS// 初始化libavformat和注册所有muxers, demuxers和协议avdevice_register_all();avformat_network_init();// 打开摄像头开始// // 摄像头支持多种参数因此使用option 指定参数 最大支持到9帧av_dict_set(options, video_size, 1280*720, 0);av_dict_set(options, framerate, 9, 0);av_dict_set(options, input_format, yuyv422, 0);AVInputFormat *input_format av_find_input_format(v4l2);if ((ret avformat_open_input(input_ctx, /dev/video0, input_format, options)) 0){fprintf(stderr, Could not open input\n);return ret;}// 查找流信息ret avformat_find_stream_info(input_ctx, NULL);if (ret 0){std::cerr could not find stream info std::endl;return -1;}// 查找视频流for (size_t i 0; i input_ctx-nb_streams; i){if (input_ctx-streams[i]-codecpar-codec_type AVMEDIA_TYPE_VIDEO){video_stream_index i;break;}}if (video_stream_index -1){std::cerr no video stream found std::endl;return -1;}// 查找编码器AVCodec *codec avcodec_find_encoder(AV_CODEC_ID_H264);if (!codec){fprintf(stderr, Could not find AV_CODEC_ID_H264\n);return -1;}// 分配编码器上下文AVCodecContext *codec_ctx avcodec_alloc_context3(codec);if (!codec_ctx){std::cerr Could not allocate video codec context std::endl;return -1;}// 设置编码器参数codec_ctx-codec_id codec-id;codec_ctx-bit_rate 400000;codec_ctx-width 1280;codec_ctx-height 720;codec_ctx-time_base (AVRational){1, 9};codec_ctx-framerate (AVRational){9, 1};codec_ctx-gop_size 10;codec_ctx-max_b_frames 0; // 不需要B帧codec_ctx-pix_fmt AV_PIX_FMT_YUV420P; // 传入的420P 格式而cam 默认输出422 则一会 需要作转换// 打开编码器if (avcodec_open2(codec_ctx, codec, NULL) 0){fprintf(stderr, Could not open codec\n);return -1;}// 分配输出上下文AVFormatContext *output_ctx nullptr;ret avformat_alloc_output_context2(output_ctx, NULL, flv, output_url);if (!output_ctx){fprintf(stderr, Could not create output context\n);return ret;}// 创建输出流AVStream *out_stream avformat_new_stream(output_ctx, codec);if (!out_stream){fprintf(stderr, Could not avformat_new_stream\n);return -1;}// 从输入流复制参数到输出流avcodec_parameters_from_context(out_stream-codecpar, codec_ctx);out_stream-time_base codec_ctx-time_base;// 打开输出URLif (!(output_ctx-oformat-flags AVFMT_NOFILE)){if (avio_open(output_ctx-pb, output_url, AVIO_FLAG_WRITE) 0){std::cerr Could not open output URL std::endl;return -1;}}// 写输出文件头if (avformat_write_header(output_ctx, NULL) 0){fprintf(stderr, Could not write header\n);return -1;}// 分配AVFrame并设置参数AVFrame *frame av_frame_alloc();if (!frame){std::cerr Could not allocate video frame std::endl;return -1;}frame-format codec_ctx-pix_fmt;frame-width codec_ctx-width;frame-height codec_ctx-height;av_frame_get_buffer(frame, 32);// 准备颜色空间色彩转换SwsContext *sws_ctx sws_getContext(codec_ctx-width, codec_ctx-height, AV_PIX_FMT_YUYV422,codec_ctx-width, codec_ctx-height, AV_PIX_FMT_YUV420P,SWS_BILINEAR, NULL, NULL, NULL);if (!sws_ctx){std::cerr Could not initialize the conversion context std::endl;return -1;}while (true){if (av_read_frame(input_ctx, pkt) 0){if (pkt.stream_index video_stream_index){// 从相机出来的原始帧 为YUV 422 需要转换为420P// 数据是YUYV422格式需要转换为YUV420PAVFrame *temp_frame av_frame_alloc();if (!temp_frame){std::cerr Could not allocate temporary frame std::endl;av_packet_unref(pkt);continue;}// 分配临时帧的内存空间if (av_image_alloc(temp_frame-data, temp_frame-linesize, codec_ctx-width, codec_ctx-height, AV_PIX_FMT_YUYV422, 1) 0){std::cerr Could not allocate temporary frame buffer std::endl;av_frame_free(temp_frame);av_packet_unref(pkt);continue;}// 将pkt.data中的数据填充到temp_frameret av_image_fill_arrays(temp_frame-data, temp_frame-linesize, pkt.data, AV_PIX_FMT_YUYV422, codec_ctx-width, codec_ctx-height, 1);if (ret 0){std::cerr Error filling arrays std::endl;av_freep(temp_frame-data[0]);av_frame_free(temp_frame);av_packet_unref(pkt);continue;}// 转换颜色空间到YUV420Psws_scale(sws_ctx, temp_frame-data, temp_frame-linesize, 0, codec_ctx-height, frame-data, frame-linesize);// 设置帧的 PTSframe-pts pts;// 编码视频数据ret avcodec_send_frame(codec_ctx, frame);if (ret 0){std::cerr Error sending frame to encoder std::endl;break;}while (ret 0){ret avcodec_receive_packet(codec_ctx, pkt);if (ret AVERROR(EAGAIN) || ret AVERROR_EOF){break;}else if (ret 0){std::cerr Error encoding frame std::endl;break;}// 将编码后的视频数据推送到RTMP服务器pkt.stream_index out_stream-index;av_packet_rescale_ts(pkt, codec_ctx-time_base, out_stream-time_base);pkt.pos -1;ret av_interleaved_write_frame(output_ctx, pkt);if (ret 0){std::cerr Error writing frame std::endl;break;}av_packet_unref(pkt);}av_frame_free(temp_frame);}}}av_write_trailer(output_ctx);// 释放资源av_frame_free(frame);avcodec_free_context(codec_ctx);avformat_close_input(input_ctx);if (output_ctx !(output_ctx-oformat-flags AVFMT_NOFILE)){avio_closep(output_ctx-pb);}avformat_free_context(output_ctx);sws_freeContext(sws_ctx);return 0;
}
5.获取推流数据
常用的工具为VLC播放器选择打开网络串流地址 就能播放推流画面了