FFmpeg写入cv::Mat向量为视频失败,VLC无法播放问题求助
问题
我实现了两个函数:一个读取视频并将帧存储为cv::Mat向量,另一个接收cv::Mat向量并写入视频。代码能编译运行无异常,但生成的视频文件有数据却无法用VLC播放。请问写视频函数存在哪些问题?
以下是我的代码:
#include <iostream> #include <string> #include <vector> #include <opencv2/core/mat.hpp> #include <opencv2/imgcodecs.hpp> extern "C" { #include <libavformat/avformat.h> #include <libavutil/imgutils.h> #include <libswscale/swscale.h> #include <libavcodec/avcodec.h> #include <libavutil/pixdesc.h> #include <libavutil/opt.h> } // helper function to check for FFmpeg errors inline void checkError(int error, const std::string &message) { if (error < 0) { std::cerr << message << ": " << av_err2str(error) << std::endl; exit(EXIT_FAILURE); } } int writeVideo(const std::string& video_path, std::vector<cv::Mat>& frames, int width, int height, int fps) { // initialize FFmpeg av_log_set_level(AV_LOG_ERROR); avformat_network_init(); // create the output video context AVFormatContext *formatContext = nullptr; int error = avformat_alloc_output_context2(&formatContext, nullptr, nullptr, video_path.c_str()); checkError(error, "Error creating output context"); // create the video stream AVStream *videoStream = avformat_new_stream(formatContext, nullptr); if (!videoStream) { std::cerr << "Error creating video stream" << std::endl; exit(EXIT_FAILURE); } // create the video codec context const AVCodec *videoCodec = avcodec_find_encoder(AV_CODEC_ID_MPEG4); AVCodecContext *videoCodecContext = avcodec_alloc_context3(videoCodec); if (!videoCodecContext) { std::cerr << "Error allocating video codec context" << std::endl; exit(EXIT_FAILURE); } videoCodecContext->bit_rate = 200000; videoCodecContext->width = width; videoCodecContext->height = height; videoCodecContext->time_base = (AVRational){ 1, fps }; videoCodecContext->framerate = (AVRational){ fps, 1 }; videoCodecContext->gop_size = 12; videoCodecContext->max_b_frames = 0; videoCodecContext->pix_fmt = AV_PIX_FMT_YUV420P; if (formatContext->oformat->flags & AVFMT_GLOBALHEADER) { videoCodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; } error = avcodec_open2(videoCodecContext, videoCodec, nullptr); checkError(error, "Error opening"); error = avcodec_parameters_from_context(videoStream->codecpar, videoCodecContext); checkError(error, "Error setting video codec parameters"); // open the output file error = avio_open(&formatContext->pb, video_path.c_str(), AVIO_FLAG_WRITE); checkError(error, "Error opening output file"); // write the video file header error = avformat_write_header(formatContext, nullptr); checkError(error, "Error writing video file header"); AVPacket *packet = av_packet_alloc(); if (!packet) { std::cerr << "Error allocating packet" << std::endl; exit(EXIT_FAILURE); } for (const cv::Mat &frame : frames) { // convert the cv::Mat to an AVFrame AVFrame *avFrame = av_frame_alloc(); avFrame->format = videoCodecContext->pix_fmt; avFrame->width = width; avFrame->height = height; error = av_frame_get_buffer(avFrame, 0); checkError(error, "Error allocating frame buffer"); struct SwsContext *frameConverter = sws_getContext(width, height, AV_PIX_FMT_BGR24, width, height, videoCodecContext->pix_fmt, SWS_BICUBIC, nullptr, nullptr, nullptr); uint8_t *srcData[AV_NUM_DATA_POINTERS] = { frame.data }; int srcLinesize[AV_NUM_DATA_POINTERS] = { static_cast<int>(frame.step) }; sws_scale(frameConverter, srcData, srcLinesize, 0, height, avFrame->data, avFrame->linesize); sws_freeContext(frameConverter); // encode the AVFrame avFrame->pts = packet->pts; error = avcodec_send_frame(videoCodecContext, avFrame); checkError(error, "Error sending frame to video codec"); while (error >= 0) { error = avcodec_receive_packet(videoCodecContext, packet); if (error == AVERROR(EAGAIN) || error == AVERROR_EOF) { break; } checkError(error, "Error encoding video frame"); // write the encoded packet to the output file packet->stream_index = videoStream->index; error = av_interleaved_write_frame(formatContext, packet); checkError(error, "Error writing video packet"); av_packet_unref(packet); } av_frame_free(&avFrame); } // clean up av_packet_free(&packet); avcodec_free_context(&videoCodecContext); avformat_free_context(formatContext); avformat_network_deinit(); return EXIT_SUCCESS; } std::vector<cv::Mat> readVideo(const std::string video_path) { // initialize FFmpeg av_log_set_level(AV_LOG_ERROR); avformat_network_init(); AVFormatContext* formatContext = nullptr; int error = avformat_open_input(&formatContext, video_path.c_str(), nullptr, nullptr); checkError(error, "Error opening input file"); //Read packets of a media file to get stream information. error = avformat_find_stream_info(formatContext, nullptr); checkError(error, "Error avformat find stream info"); // find the video stream AVStream* videoStream = nullptr; for (unsigned int i = 0; i < formatContext->nb_streams; i++) { if (formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && !videoStream) { videoStream = formatContext->streams[i]; } } if (!videoStream) { std::cerr << "Error: input file does not contain a video stream" << std::endl; exit(EXIT_FAILURE); } // create the video codec context const AVCodec* videoCodec = avcodec_find_decoder(videoStream->codecpar->codec_id); AVCodecContext* videoCodecContext = avcodec_alloc_context3(videoCodec); if (!videoCodecContext) { std::cerr << "Error allocating video codec context" << std::endl; exit(EXIT_FAILURE); } std::cout << "::informations::\n"; std::cout << " bit_rate:" << videoCodecContext->bit_rate << "\n"; std::cout << " width:" << videoCodecContext->width << "\n"; std::cout << " height:" << videoCodecContext->height << "\n"; std::cout << " gop_size:" << videoCodecContext->gop_size << "\n"; std::cout << " max_b_frames:" << videoCodecContext->max_b_frames << "\n"; std::cout << " pix_fmt:" << videoCodecContext->pix_fmt << "\n"; error = avcodec_parameters_to_context(videoCodecContext, videoStream->codecpar); checkError(error, "Error setting video codec context parameters"); error = avcodec_open2(videoCodecContext, videoCodec, nullptr); checkError(error, "Error opening video codec"); // create the frame scaler int width = videoCodecContext->width; int height = videoCodecContext->height; struct SwsContext* frameScaler = sws_getContext(width, height, videoCodecContext->pix_fmt, width, height, AV_PIX_FMT_BGR24, SWS_BICUBIC, nullptr, nullptr, nullptr); // read the packets and decode the video frames std::vector<cv::Mat> videoFrames; AVPacket packet; while (av_read_frame(formatContext, &packet) == 0) { if (packet.stream_index == videoStream->index) { // decode the video frame AVFrame* frame = av_frame_alloc(); int gotFrame = 0; error = avcodec_send_packet(videoCodecContext, &packet); checkError(error, "Error sending packet to video codec"); error = avcodec_receive_frame(videoCodecContext, frame); //There is not enough data for decoding the frame, have to free and get more data if (error == AVERROR(EAGAIN)) { av_frame_unref(frame); av_freep(frame); continue; } if (error == AVERROR_EOF) { std::cerr << "AVERROR_EOF" << std::endl; break; } checkError(error, "Error receiving frame from video codec"); if (error == 0) { gotFrame = 1; } if (gotFrame) { // scale the frame to the desired format AVFrame* scaledFrame = av_frame_alloc(); av_image_alloc(scaledFrame->data, scaledFrame->linesize, width, height, AV_PIX_FMT_BGR24, 32); sws_scale(frameScaler, frame->data, frame->linesize, 0, height, scaledFrame->data, scaledFrame->linesize); // copy the frame data to a cv::Mat object cv::Mat mat(height, width, CV_8UC3, scaledFrame->data[0], scaledFrame->linesize[0]); videoFrames.push_back(mat.clone()); // clean up av_freep(&scaledFrame->data[0]); av_frame_free(&scaledFrame); } av_frame_free(&frame); } av_packet_unref(&packet); } // clean up sws_freeContext(frameScaler); avcodec_free_context(&videoCodecContext); avformat_close_input(&formatContext); return videoFrames; } int main() { auto videoFrames = readVideo("input.mp4"); cv::imwrite("test.png", videoFrames[10]); writeVideo("outnow.mp4", videoFrames, 512, 608, 30); //writeVideo("outnow.mp4", videoFrames); return 0; }
问题分析与修复
你的writeVideo函数存在以下关键问题,导致生成的视频无法被播放器识别:
帧PTS时间戳错误
原代码中avFrame->pts = packet->pts;完全错误:初始packet的pts未定义,且帧的PTS需要按序列递增,基于编码器的时间基计算。需要维护一个帧计数器,每帧递增后转换为对应时间基的PTS。未处理编码器剩余数据包
所有帧发送完毕后,编码器可能还有缓存的数据包未输出,必须调用avcodec_send_frame(videoCodecContext, nullptr)触发编码器输出剩余内容。未写入视频文件尾
写完所有数据包后,必须调用av_write_trailer(formatContext)写入容器文件尾,这是播放器识别视频的必要条件。重复创建销毁SwsContext
每帧创建销毁格式转换上下文会降低性能,应该在循环外创建,循环结束后销毁。未关闭输出文件
用avio_open打开的文件需要显式调用avio_close关闭,避免资源泄漏。
修复后的writeVideo函数:
int writeVideo(const std::string& video_path, std::vector<cv::Mat>& frames, int width, int height, int fps) { // initialize FFmpeg av_log_set_level(AV_LOG_ERROR); avformat_network_init(); // create the output video context AVFormatContext *formatContext = nullptr; int error = avformat_alloc_output_context2(&formatContext, nullptr, nullptr, video_path.c_str()); checkError(error, "Error creating output context"); // create the video stream AVStream *videoStream = avformat_new_stream(formatContext, nullptr); if (!videoStream) { std::cerr << "Error creating video stream" << std::endl; exit(EXIT_FAILURE); } // create the video codec context const AVCodec *videoCodec = avcodec_find_encoder(AV_CODEC_ID_MPEG4); AVCodecContext *videoCodecContext = avcodec_alloc_context3(videoCodec); if (!videoCodecContext) { std::cerr << "Error allocating video codec context" << std::endl; exit(EXIT_FAILURE); } videoCodecContext->bit_rate = 200000; videoCodecContext->width = width; videoCodecContext->height = height; videoCodecContext->time_base = (AVRational){ 1, fps }; videoCodecContext->framerate = (AVRational){ fps, 1 }; videoCodecContext->gop_size = 12; videoCodecContext->max_b_frames = 0; videoCodecContext->pix_fmt = AV_PIX_FMT_YUV420P; if (formatContext->oformat->flags & AVFMT_GLOBALHEADER) { videoCodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; } error = avcodec_open2(videoCodecContext, videoCodec, nullptr); checkError(error, "Error opening codec"); error = avcodec_parameters_from_context(videoStream->codecpar, videoCodecContext); checkError(error, "Error setting video codec parameters"); // 设置流的时间基 videoStream->time_base = videoCodecContext->time_base; // open the output file error = avio_open(&formatContext->pb, video_path.c_str(), AVIO_FLAG_WRITE); checkError(error, "Error opening output file"); // write the video file header error = avformat_write_header(formatContext, nullptr); checkError(error, "Error writing video file header"); AVPacket *packet = av_packet_alloc(); if (!packet) { std::cerr << "Error allocating packet" << std::endl; exit(EXIT_FAILURE); } // 提前创建格式转换上下文 struct SwsContext *frameConverter = sws_getContext(width, height, AV_PIX_FMT_BGR24, width, height, videoCodecContext->pix_fmt, SWS_BICUBIC, nullptr, nullptr, nullptr); if (!frameConverter) { std::cerr << "Error creating frame converter" << std::endl; exit(EXIT_FAILURE); } int64_t frame_idx = 0; for (const cv::Mat &frame : frames) { // convert the cv::Mat to an AVFrame AVFrame *avFrame = av_frame_alloc(); avFrame->format = videoCodecContext->pix_fmt; avFrame->width = width; avFrame->height = height; error = av_frame_get_buffer(avFrame, 0); checkError(error, "Error allocating frame buffer"); uint8_t *srcData[AV_NUM_DATA_POINTERS] = { frame.data }; int srcLinesize[AV_NUM_DATA_POINTERS] = { static_cast<int>(frame.step) }; sws_scale(frameConverter, srcData, srcLinesize, 0, height, avFrame->data, avFrame->linesize); // 设置正确的PTS时间戳 avFrame->pts = frame_idx++; error = avcodec_send_frame(videoCodecContext, avFrame); checkError(error, "Error sending frame to video codec"); while (error >= 0) { error = avcodec_receive_packet(videoCodecContext, packet); if (error == AVERROR(EAGAIN) || error == AVERROR_EOF) { break; } checkError(error, "Error encoding video frame"); // 转换PTS到流的时间基 av_packet_rescale_ts(packet, videoCodecContext->time_base, videoStream->time_base); packet->stream_index = videoStream->index; error = av_interleaved_write_frame(formatContext, packet); checkError(error, "Error writing video packet"); av_packet_unref(packet); } av_frame_free(&avFrame); } // 发送空帧触发编码器输出剩余数据包 error = avcodec_send_frame(videoCodecContext, nullptr); checkError(error, "Error sending empty frame to codec"); while (error >= 0) {
相关产品推荐
相关产品推荐

