一.ffmpeg的推流流程
1. 初始化及配置参数,全局头信息
1.1 初始化FFmpeg的网络模块。
1.2 创建输出上下文,指定H.264编码器。
avformat_alloc_output_context2(&fmt_ctx, nullptr, "rtsp", rtsp_url.c_str())
codec = avcodec_find_encoder(AV_CODEC_ID_H264);
video_stream = avformat_new_stream(fmt_ctx, codec);
codec_ctx = avcodec_alloc_context3(codec);
1.3 配置编码器参数以及格式上下文。
codec_ctx->codec_id = codec->id;codec_ctx->codec_type = AVMEDIA_TYPE_VIDEO;codec_ctx->width = width;codec_ctx->height = height;codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;codec_ctx->time_base = {1, fps};codec_ctx->gop_size = 12;
avcodec_open2(codec_ctx, codec, nullptr)avcodec_parameters_from_context(video_stream->codecpar, codec_ctx);
1.4 全局信息置于流开头,将格式上下文的头信息写入输出流,设置视频流参数
if (fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) {codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;}av_opt_set(fmt_ctx->priv_data, "rtsp_transport", "tcp", 0); av_opt_set(fmt_ctx->priv_data, "max_delay", "500000",0);
avformat_write_header(fmt_ctx, nullptr)
1.5 申请frame用于后续推送,初始化用于颜色格式转换的SWS上下文。
av_frame = av_frame_alloc();
av_frame_get_buffer(av_frame, 32) sws_ctx_422_to_420 =sws_getContext(width, height, AV_PIX_FMT_YUYV422, width, height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, nullptr, nullptr, nullptr);
2. 编码及推送一帧数据,计算pts
av_frame->pts =av_rescale_q(frame_count++, (AVRational){1, 25}, video_stream->time_base);avcodec_send_frame(codec_ctx, av_frame) av_init_packet(&pkt);avcodec_receive_packet(codec_ctx, &pkt)av_interleaved_write_frame(fmt_ctx, &pkt)av_packet_unref(&pkt);
二.需要注意的点
- avio_open函数不需要调用,rtmp有flv协议,但是rtsp并没有规定具体协议,在申请输出控制上下文时,ffmpeg将自动创建该io上下文。解答
- 设置PTS,确保与时间基匹配
- AVPacket的释放 av_packet_unref
三.后续改进
四.源码
#ifndef FFMPEGRTSPSTREAMER_H
#define FFMPEGRTSPSTREAMER_H#pragma onceextern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libavutil/opt.h>
#include <libswscale/swscale.h>
}#include <cstring>
#include <iostream>struct YUVFrame {unsigned char* data;int width;int height;
};class FFmpegRTSPStreamer {public:FFmpegRTSPStreamer(const std::string& rtsp_url, int width, int height,int fps);~FFmpegRTSPStreamer();bool init();bool push_frame(const YUVFrame& frame);void cleanup();void YUV422ToYUV420p(const unsigned char* yuv422, unsigned char* yuv420,int width, int height);void YUV422ToYUV420pBySWS(const uint8_t* yuv422, uint8_t* yuv420, int width,int height);void SaveYUV420pToFile(const uint8_t* yuv420p, int width, int height,const std::string& filename);void SaveYUV422pToFile(const uint8_t* yuv422p, int width, int height,const std::string& filename);void ConvertYUYVToYUV420P(const unsigned char* yuyv, unsigned char* yuv420p,int width, int height);private:std::string rtsp_url;int width;int height;int fps;unsigned long long frame_count = 0;AVFormatContext* fmt_ctx = nullptr;AVStream* video_stream = nullptr;AVCodecContext* codec_ctx = nullptr;AVCodec* codec = nullptr;AVFrame* av_frame = nullptr;SwsContext* sws_ctx_422_to_420 = nullptr;
};#endif
#include "FFmpegRTSPStreamer.h"#include <fstream>
#include <vector>FFmpegRTSPStreamer::FFmpegRTSPStreamer(const std::string& rtsp_url, int width,int height, int fps): rtsp_url(rtsp_url),width(width),height(height),fps(fps),fmt_ctx(nullptr),video_stream(nullptr),codec_ctx(nullptr),codec(nullptr),av_frame(nullptr) {}FFmpegRTSPStreamer::~FFmpegRTSPStreamer() { cleanup(); }bool FFmpegRTSPStreamer::init() {avformat_network_init();if (avformat_alloc_output_context2(&fmt_ctx, nullptr, "rtsp",rtsp_url.c_str()) < 0) {std::cerr << "Could not allocate output context." << std::endl;return false;}codec = avcodec_find_encoder(AV_CODEC_ID_H264);if (!codec) {std::cerr << "H264 codec not found." << std::endl;return false;}video_stream = avformat_new_stream(fmt_ctx, codec);if (!video_stream) {std::cerr << "Failed to create video stream." << std::endl;return false;}codec_ctx = avcodec_alloc_context3(codec);if (!codec_ctx) {std::cerr << "Failed to allocate codec context." << std::endl;return false;}codec_ctx->codec_id = codec->id;codec_ctx->codec_type = AVMEDIA_TYPE_VIDEO;codec_ctx->width = width;codec_ctx->height = height;codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;codec_ctx->time_base = {1, fps};codec_ctx->gop_size = 12;if (fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) {codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;}if (avcodec_open2(codec_ctx, codec, nullptr) < 0) {std::cerr << "Failed to open codec." << std::endl;return false;}avcodec_parameters_from_context(video_stream->codecpar, codec_ctx);av_opt_set(fmt_ctx->priv_data, "rtsp_transport", "tcp", 0); av_opt_set(fmt_ctx->priv_data, "max_delay", "500000",0); if (avformat_write_header(fmt_ctx, nullptr) < 0) {std::cerr << "Error occurred when writing header." << std::endl;return false;}av_frame = av_frame_alloc();av_frame->format = codec_ctx->pix_fmt;av_frame->width = width;av_frame->height = height;if (av_frame_get_buffer(av_frame, 32) < 0) {std::cerr << "Could not allocate frame buffer." << std::endl;return false;}sws_ctx_422_to_420 =sws_getContext(width, height, AV_PIX_FMT_YUYV422, width, height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, nullptr, nullptr, nullptr);if (!sws_ctx_422_to_420) {std::cerr << "Error initializing the conversion context sws_ctx_422_to_420."<< std::endl;return false;}return true;
}bool FFmpegRTSPStreamer::push_frame(const YUVFrame& frame) {if (!av_frame || !frame.data) {std::cerr << "Invalid frame or uninitialized streamer." << std::endl;return false;}memcpy(av_frame->data[0], frame.data, width * height); memcpy(av_frame->data[1], frame.data + width * height,width * height / 4); memcpy(av_frame->data[2], frame.data + width * height * 5 / 4,width * height / 4); av_frame->pts =av_rescale_q(frame_count++, (AVRational){1, 25}, video_stream->time_base);if (avcodec_send_frame(codec_ctx, av_frame) < 0) {std::cerr << "Failed to send frame to ""encoder."<< std::endl;return false;}AVPacket pkt;av_init_packet(&pkt);pkt.data = nullptr;pkt.size = 0;if (avcodec_receive_packet(codec_ctx, &pkt) == 0) {pkt.stream_index = video_stream->index;std::cout << "frame_count:" << frame_count << " PTS: " << pkt.pts<< " DTS: " << pkt.dts << " size: " << pkt.size << std::endl;if (av_interleaved_write_frame(fmt_ctx, &pkt) < 0) {std::cerr << "Failed to write frame. PTS: " << pkt.pts<< " DTS: " << pkt.dts << " size: " << pkt.size << std::endl;av_packet_unref(&pkt);return false;}av_packet_unref(&pkt);}return true;
}void FFmpegRTSPStreamer::cleanup() {if (fmt_ctx) {av_write_trailer(fmt_ctx);if (fmt_ctx->pb) {avio_close(fmt_ctx->pb);}avformat_free_context(fmt_ctx);fmt_ctx = nullptr;}if (codec_ctx) {avcodec_free_context(&codec_ctx);codec_ctx = nullptr;}if (av_frame) {av_frame_free(&av_frame);av_frame = nullptr;}if (sws_ctx_422_to_420) {sws_freeContext(sws_ctx_422_to_420);sws_ctx_422_to_420 = nullptr;}
}void FFmpegRTSPStreamer::YUV422ToYUV420p(const unsigned char* yuv422,unsigned char* yuv420, int width,int height) {int y_size = width * height;int uv_size = width * height / 4; const unsigned char* y_data = yuv422; const unsigned char* u_data = yuv422 + y_size; const unsigned char* v_data =u_data + (width * height) / 2; unsigned char* y_out = yuv420; unsigned char* u_out = yuv420 + y_size; unsigned char* v_out = u_out + uv_size; memcpy(y_out, y_data, y_size);for (int i = 0; i < height / 2; i++) {for (int j = 0; j < width / 2; j++) {int index422 = 2 * (i * width + j); int index420_u = i * (width / 2) + j;int index420_v = index420_u;u_out[index420_u] = (u_data[index422] + u_data[index422 + 1]) / 2;v_out[index420_v] = (v_data[index422] + v_data[index422 + 1]) / 2;}}
}void FFmpegRTSPStreamer::YUV422ToYUV420pBySWS(const uint8_t* yuv422,uint8_t* yuv420, int width,int height) {uint8_t* src_data[4] = {const_cast<uint8_t*>(yuv422), nullptr, nullptr,nullptr}; int src_linesize[4] = {width, width / 2, width / 2, 0}; uint8_t* dst_data[4] = {yuv420, nullptr, nullptr, nullptr}; int dst_linesize[4] = {width, width / 2, width / 2, 0}; int ret = sws_scale(sws_ctx_422_to_420, src_data, src_linesize, 0, height, dst_data, dst_linesize );if (ret < 0) {std::cerr << "Error during conversion." << std::endl;}
}void FFmpegRTSPStreamer::SaveYUV420pToFile(const uint8_t* yuv420p, int width,int height,const std::string& filename) {std::ofstream file(filename, std::ios::binary);if (!file) {std::cerr << "无法打开文件:" << filename << std::endl;return;}int y_size = width * height; int uv_size =width * height / 4; file.write(reinterpret_cast<const char*>(yuv420p), y_size); file.write(reinterpret_cast<const char*>(yuv420p + y_size),uv_size); file.write(reinterpret_cast<const char*>(yuv420p + y_size + uv_size),uv_size); file.close();std::cout << "YUV 数据已保存为 " << filename << std::endl;
}void FFmpegRTSPStreamer::SaveYUV422pToFile(const uint8_t* yuv422p, int width,int height,const std::string& filename) {std::ofstream file(filename, std::ios::binary);if (!file) {std::cerr << "无法打开文件:" << filename << std::endl;return;}int y_size = width * height; int uv_size = width * height / 2;file.write(reinterpret_cast<const char*>(yuv422p), y_size); file.write(reinterpret_cast<const char*>(yuv422p + y_size),uv_size); file.write(reinterpret_cast<const char*>(yuv422p + y_size + uv_size),uv_size); file.close();std::cout << "YUV 数据已保存为 " << filename << std::endl;
}void FFmpegRTSPStreamer::ConvertYUYVToYUV420P(const unsigned char* yuyv,unsigned char* yuv420p, int width,int height) {int frameSize = width * height;unsigned char* yPlane = yuv420p; unsigned char* uPlane = yuv420p + frameSize; unsigned char* vPlane = yuv420p + frameSize * 5 / 4; memset(yPlane, 0, frameSize);memset(uPlane, 0, frameSize / 4);memset(vPlane, 0, frameSize / 4);for (int j = 0; j < height; j++) {for (int i = 0; i < width; i += 2) {int yuyvIndex = (j * width + i) * 2;unsigned char y1 = yuyv[yuyvIndex]; unsigned char u = yuyv[yuyvIndex + 1]; unsigned char y2 = yuyv[yuyvIndex + 2]; unsigned char v = yuyv[yuyvIndex + 3]; yPlane[j * width + i] = y1;yPlane[j * width + i + 1] = y2;if (j % 2 == 0 && i % 2 == 0) {int uvIndex = (j / 2) * (width / 2) + (i / 2);uPlane[uvIndex] = u;vPlane[uvIndex] = v;}}}
}