FFmpeg實現(xiàn)將編碼后數(shù)據(jù)保存成mp4
以下測試代碼實現(xiàn)的功能是:持續(xù)從內(nèi)存塊中獲取原始數(shù)據(jù),然后依次進行解碼、編碼、最后保存成mp4視頻文件。
可保存成單個視頻文件,也可指定每個視頻文件的總幀數(shù),保存多個視頻文件。
為了便于查看和修改,這里將可獨立的程序段存放在單個函數(shù)中:
1.線程函數(shù)set_packet:獨立線程,用于用戶向指定的內(nèi)存塊中持續(xù)寫入數(shù)據(jù),這里通過調(diào)用隊列類PacketScaleQueue,可參考:FFmpeg中AVIOContext的使用方法詳解中說明。這里填充的數(shù)據(jù)參考FFmpeg源碼中的doc/examples/encode_video.c:每次向隊列中寫入一幀數(shù)據(jù)
void set_packet(PacketScaleQueue& packet_encode) { while (packet_encode_flag) { Buffer buffer; packet_encode.popPacket(buffer); static int i = 0; // refrence: ffmpeg/doc/examples/encode_video.c // prepare a dummy image: Y unsigned char* p1 = buffer.data; for (auto y = 0; y < height; ++y) { for (auto x = 0; x < width; ++x) { p1[y * width + x] = x + y + i * 3; } } // Cb and Cr unsigned char* p2 = buffer.data + width * height; unsigned char* p3 = buffer.data + width * height + width * height / 4; for (auto y = 0; y < height / 2; ++y) { for (auto x = 0; x < width / 2; ++x) { p2[y * width / 2 + x] = 128 + y + i * 2; p3[y * width / 2 + x] = 64 + x + i * 5; } } packet_encode.pushScale(buffer); if (++i > 25) i = 0; std::this_thread::sleep_for(std::chrono::milliseconds(40)); } }
2.回調(diào)函數(shù)read_packet:avio_alloc_context中使用,不斷從隊列中獲取數(shù)據(jù),av_read_frame中也會從這里獲取數(shù)據(jù):每次從隊列中獲取一幀數(shù)據(jù)
int read_packet(void* opaque, uint8_t* buf, int buf_size) { PacketScaleQueue* packet_encode = static_cast<PacketScaleQueue*>(opaque); Buffer buffer; packet_encode->popScale(buffer); memcpy(buf, buffer.data, buf_size); packet_encode->pushPacket(buffer); return buf_size; }
3.函數(shù)get_input_format_context:創(chuàng)建輸入AVFormatContext,需要調(diào)用av_dict_set設置video_size和pixel_format
AVFormatContext* get_input_format_context(AVIOContext* avio_ctx) { AVFormatContext* ifmt_ctx = avformat_alloc_context(); if (!ifmt_ctx) { print_error_string(AVERROR(ENOMEM)); return nullptr; } ifmt_ctx->pb = avio_ctx; AVDictionary* dict = nullptr; av_dict_set(&dict, "video_size", video_size, 0); av_dict_set(&dict, "pixel_format", pixel_format, 0); auto ret = avformat_open_input(&ifmt_ctx, nullptr, av_find_input_format("rawvideo"), &dict); if (ret < 0) { fprintf(stderr, "Could not open input\n"); print_error_string(ret); return nullptr; } av_dict_free(&dict); ret = avformat_find_stream_info(ifmt_ctx, nullptr); if (ret < 0) { fprintf(stderr, "Could not find stream information\n"); print_error_string(ret); return nullptr; } for (unsigned int i = 0; i < ifmt_ctx->nb_streams; ++i) { const AVStream* stream = ifmt_ctx->streams[i]; if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { video_stream_index = i; fprintf(stdout, "type of the encoded data: %d, dimensions of the video frame in pixels: width: %d, height: %d, pixel format: %d\n", stream->codecpar->codec_id, stream->codecpar->width, stream->codecpar->height, stream->codecpar->format); } } if (video_stream_index == -1) { fprintf(stderr, "error: no video stream\n"); return nullptr; } if (ifmt_ctx->streams[video_stream_index]->codecpar->codec_id != AV_CODEC_ID_RAWVIDEO) { fprintf(stderr, "error: this test code only support rawvideo encode: %d\n", ifmt_ctx->streams[video_stream_index]->codecpar->codec_id); return nullptr; } av_dump_format(ifmt_ctx, 0, "nothing", 0); return ifmt_ctx; }
4.函數(shù)get_decode_context:創(chuàng)建解碼AVCodecContext
AVCodecContext* get_decode_context(AVFormatContext* ifmt_ctx) { AVCodec* decoder = nullptr; auto ret = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &decoder, 0); if (ret < 0) { fprintf(stderr, "fail to av_find_best_stream: %d\n", ret); print_error_string(ret); return nullptr; } AVCodecContext* dec_ctx = avcodec_alloc_context3(decoder); if (!dec_ctx) { fprintf(stderr, "fail to avcodec_alloc_context3\n"); return nullptr; } ret = avcodec_parameters_to_context(dec_ctx, ifmt_ctx->streams[video_stream_index]->codecpar); if (ret < 0) { fprintf(stderr, "fail to avcodec_parameters_to_context: %d\n", ret); print_error_string(ret); return nullptr; } dec_ctx->framerate = av_guess_frame_rate(ifmt_ctx, ifmt_ctx->streams[video_stream_index], nullptr); ret = avcodec_open2(dec_ctx, decoder, nullptr); if (ret != 0) { fprintf(stderr, "fail to avcodec_open2: %d\n", ret); print_error_string(ret); return nullptr; } return dec_ctx; }
5.函數(shù)get_encode_context:創(chuàng)建編碼AVCodecContext,注意對enc_ctx的相關成員的賦值
AVCodecContext* get_encode_context(AVFormatContext* ifmt_ctx) { AVCodec* encodec = avcodec_find_encoder_by_name("mpeg1video"); // ffmpeg.exe -encoders if (!encodec) { fprintf(stderr, "fail to avcodec_find_encoder_by_name\n"); return nullptr; } AVCodecContext* enc_ctx = avcodec_alloc_context3(encodec); if (!enc_ctx) { fprintf(stderr, "fail to avcodec_alloc_context3\n"); return nullptr; } enc_ctx->bit_rate = 400000; enc_ctx->framerate = ifmt_ctx->streams[video_stream_index]->r_frame_rate; enc_ctx->pix_fmt = AV_PIX_FMT_YUV420P; enc_ctx->height = ifmt_ctx->streams[video_stream_index]->codecpar->height; enc_ctx->width = ifmt_ctx->streams[video_stream_index]->codecpar->width; enc_ctx->time_base = av_inv_q(av_d2q(frame_rate, 4096)); enc_ctx->max_b_frames = 1; //if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) // true enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; auto ret = avcodec_open2(enc_ctx, encodec, nullptr); if (ret != 0) { fprintf(stderr, "fail to avcodec_open2: %d\n", ret); print_error_string(ret); return nullptr; } return enc_ctx; }
6.函數(shù)get_output_format_context:創(chuàng)建輸出AVFormatContext
AVFormatContext* get_output_format_context(const AVCodecContext* enc_ctx, const char* filename) { AVFormatContext* ofmt_ctx = nullptr; auto ret = avformat_alloc_output_context2(&ofmt_ctx, nullptr, nullptr, filename); if (ret < 0 || !ofmt_ctx) { fprintf(stderr, "fail to avformat_alloc_output_context2: %d\n", ret); print_error_string(ret); return nullptr; } AVStream* out_stream = avformat_new_stream(ofmt_ctx, nullptr); if (!out_stream) { fprintf(stderr, "fail to avformat_new_stream\n"); return nullptr; } ret = avcodec_parameters_from_context(out_stream->codecpar, enc_ctx); if (ret < 0) { fprintf(stderr, "fail to avcodec_parameters_from_context: %d\n", ret); print_error_string(ret); return nullptr; } out_stream->time_base = enc_ctx->time_base; if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE)) { // true ret = avio_open(&ofmt_ctx->pb, filename, AVIO_FLAG_WRITE); if (ret < 0) { fprintf(stderr, "fail to avio_open: %d\n", ret); print_error_string(ret); return nullptr; } } ret = avformat_write_header(ofmt_ctx, nullptr); if (ret < 0) { fprintf(stderr, "fail to avformat_write_header: %d\n", ret); print_error_string(ret); return nullptr; } av_dump_format(ofmt_ctx, 0, filename, 1); return ofmt_ctx; }
7.函數(shù)decode: 注意對packet的判斷,它有可能為nullptr;調(diào)用一次avcodec_send_packet,可能需調(diào)用多次avcodec_receive_frame,因此需要將avcodec_receive_frame放在while中;需要對dec_ctx->time_base進行設置
int decode(AVPacket* packet, AVFormatContext* ifmt_ctx, AVCodecContext* dec_ctx, AVFrame* frame) { if (packet) av_packet_rescale_ts(packet, ifmt_ctx->streams[video_stream_index]->time_base, dec_ctx->time_base); auto ret = avcodec_send_packet(dec_ctx, packet); if (ret < 0) { fprintf(stderr, "fail to avcodec_send_packet: %d\n", ret); print_error_string(ret); return -1; } while (1) { ret = avcodec_receive_frame(dec_ctx, frame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { // AVERROR(EAGAIN): decode is not yet complete //fprintf(stderr, "Warning: avcodec_receive_frame: %d\n", ret); //print_error_string(ret); break; } else if (ret < 0) { fprintf(stderr, "fail to avcodec_receive_frame: %d\n", ret); print_error_string(ret); return ret; } frame->pts = frame->best_effort_timestamp; break; } if (packet) av_packet_unref(packet); return 0; }
8.函數(shù)encode:注意:調(diào)用一次avcodec_send_frame,可能需要調(diào)用多次avcodec_receive_packet,因此需要將avcodec_receive_packet放在while中;需要對ofmt_ctx->streams[0]->time_base進行設置,否則生成的mp4中無法獲取幀速率;也可在調(diào)用avformat_write_header時設置video_track_timescale指定
int encode(AVCodecContext* enc_ctx, AVFrame* frame, AVPacket* packet, AVFormatContext* ifmt_ctx, AVFormatContext* ofmt_ctx) { auto ret = avcodec_send_frame(enc_ctx, frame); if (ret < 0) { if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { fprintf(stderr, "Warning: avcodec_send_frame: %d\n", ret); print_error_string(ret); ret = 0; } else { fprintf(stderr, "fail to avcodec_send_frame: %d\n", ret); print_error_string(ret); return ret; } } while (1) { ret = avcodec_receive_packet(enc_ctx, packet); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { // AVERROR(EAGAIN): encode is not yet complete //fprintf(stderr, "Warning: avcodec_receive_packet: %d\n", ret); //print_error_string(ret); break; } if (ret < 0) { fprintf(stderr, "fail to avcodec_receive_packet: %d\n", ret); print_error_string(ret); return ret; } packet->stream_index = 0; av_packet_rescale_ts(packet, ifmt_ctx->streams[video_stream_index]->time_base, ofmt_ctx->streams[0]->time_base); //packet2->pts = packet2->dts = frame->pts * // ofmt_ctx->streams[0]->time_base.den / ofmt_ctx->streams[0]->time_base.num / // (enc_ctx->framerate.num / enc_ctx->framerate.den); ret = av_interleaved_write_frame(ofmt_ctx, packet); if (ret < 0) { print_error_string(ret); return ret; } av_packet_unref(packet); } return 0; }
9.主函數(shù)test_ffmpeg_save_video_slice:注意:在退出前需要flush decoder和encoder;寫入視頻文件需先調(diào)用avformat_write_header,然后持續(xù)調(diào)用av_interleaved_write_frame,退出前再需調(diào)用av_write_trailer;每次新文件的寫入,需重新調(diào)用get_decode_context、get_encode_context、get_output_format_context并在之前作相應free
int test_ffmpeg_save_video_slice() { PacketScaleQueue packet_encode; packet_encode.init(queue_size, block_size); std::thread thread_packet(set_packet, std::ref(packet_encode)); uint8_t* avio_ctx_buffer = static_cast<uint8_t*>(av_malloc(block_size)); if (!avio_ctx_buffer) { print_error_string(AVERROR(ENOMEM)); return -1; } AVIOContext* avio_ctx = avio_alloc_context(avio_ctx_buffer, block_size, 0, &packet_encode, &read_packet, nullptr, nullptr); if (!avio_ctx) { print_error_string(AVERROR(ENOMEM)); return -1; } AVFormatContext* ifmt_ctx = get_input_format_context(avio_ctx); if (!ifmt_ctx) { fprintf(stderr, "fail to get_input_format_context\n"); return -1; } AVPacket *packet = av_packet_alloc(), *packet2 = av_packet_alloc(); if (!packet || !packet2) { fprintf(stderr, "fail to av_packet_alloc\n"); return -1; } AVFrame* frame = av_frame_alloc(); if (!frame) { fprintf(stderr, "fail to av_frame_alloc\n"); return -1; } int count = 0, name = 1; AVCodecContext* enc_ctx = nullptr; AVFormatContext* ofmt_ctx = nullptr; AVCodecContext* dec_ctx = nullptr; while (1) { auto ret = av_read_frame(ifmt_ctx, packet); if (ret < 0) { break; } if (packet->stream_index != video_stream_index) { av_packet_unref(packet); continue; } if (count % slice_size == 0) { enc_ctx = get_encode_context(ifmt_ctx); if (!enc_ctx) { fprintf(stderr, "fail to avcodec_alloc_context3\n"); return -1; } std::string filename = std::to_string(name) + ".mp4"; filename = std::string(path) + filename; ofmt_ctx = get_output_format_context(enc_ctx, filename.c_str()); if (!ofmt_ctx) { fprintf(stderr, "fail to get_output_format_context\n"); return -1; } dec_ctx = get_decode_context(ifmt_ctx); if (!dec_ctx) { fprintf(stderr, "fail to get_decode_context\n"); return -1; } ++name; } if (decode(packet, ifmt_ctx, dec_ctx, frame) != 0) return -1; if (encode(enc_ctx, frame, packet2, ifmt_ctx, ofmt_ctx) != 0) return -1; //fprintf(stdout, "count: %d\n", count); if (count + 1 == total_frames) { // terminate loop packet_encode_flag = false; // flush the decoder decode(nullptr, ifmt_ctx, dec_ctx, frame); if (frame->data[0]) encode(enc_ctx, frame, packet2, ifmt_ctx, ofmt_ctx); // flush the encoder encode(enc_ctx, nullptr, packet2, ifmt_ctx, ofmt_ctx); av_write_trailer(ofmt_ctx); break; } ++count; if (count > 1 && count % slice_size == 0) { // flush the decoder decode(nullptr, ifmt_ctx, dec_ctx, frame); if (frame->data[0]) encode(enc_ctx, frame, packet2, ifmt_ctx, ofmt_ctx); // flush the encoder encode(enc_ctx, nullptr, packet2, ifmt_ctx, ofmt_ctx); av_write_trailer(ofmt_ctx); avcodec_free_context(&dec_ctx); avcodec_free_context(&enc_ctx); avio_closep(&ofmt_ctx->pb); avformat_free_context(ofmt_ctx); } } av_packet_free(&packet); av_packet_free(&packet2); av_frame_free(&frame); avcodec_free_context(&dec_ctx); avcodec_free_context(&enc_ctx); avio_closep(&ofmt_ctx->pb); avformat_close_input(&ofmt_ctx); avformat_close_input(&ifmt_ctx); // note: the internal buffer could have changed, and be != avio_ctx_buffer if (avio_ctx) { av_freep(&avio_ctx->buffer); av_freep(&avio_ctx); } thread_packet.join(); fprintf(stdout, "test finish\n"); return 0; }
執(zhí)行結果如下圖所示:
可調(diào)用ffprobe.exe驗證每個生成的視頻文件的總幀數(shù),如1.mp4,執(zhí)行如下命令:
ffprobe.exe -v error -select_streams v:0 -show_entries stream=nb_frames -of default=nokey=1:noprint_wrappers=1 1.mp4
GitHub:https://github.com/fengbingchun/OpenCV_Test
到此這篇關于FFmpeg實現(xiàn)將編碼后數(shù)據(jù)保存成mp4的文章就介紹到這了,更多相關FFmpeg數(shù)據(jù)保存為mp4內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
c++中的內(nèi)聯(lián)函數(shù)inline用法實例
在本篇文章里小編給大家整理的是關于c++中的內(nèi)聯(lián)函數(shù)inline用法實例以及相關知識點,有需要的朋友們學習下。2019-09-09C語言之整數(shù)與浮點數(shù)運算的類型轉(zhuǎn)換規(guī)則詳解
這篇文章主要介紹了C語言之整數(shù)與浮點數(shù)運算的類型轉(zhuǎn)換規(guī)則,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-03-03C++ leetcode之刪除并獲得點數(shù)的示例代碼
這篇文章主要介紹了C++ leetcode之刪除并獲得點數(shù)的示例代碼,本文給大家分享問題解析及解決方案,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-05-05