FFmpeg實(shí)現(xiàn)多線程編碼并保存mp4文件
之前介紹的示例:
(1).FFmpeg實(shí)現(xiàn)將編碼后數(shù)據(jù)保存成mp4中對(duì)編碼后數(shù)據(jù)保存成mp4
(2).FFmpeg中AVIOContext的使用方法詳解中通過AVIOContext實(shí)現(xiàn)從內(nèi)存讀取數(shù)據(jù)
(3).FFmpeg中avfilter模塊的介紹與使用中將圖像加載到視頻中
這里將三部分整合到類中,便于后面增加測(cè)試代碼,下面的示例是兩個(gè)線程:從內(nèi)存中讀取數(shù)據(jù),并將指定的圖像加載到視頻,將結(jié)果保存成mp4。
示例代碼如下:
1. 類PacketScaleQueue:用于持續(xù)的從指定內(nèi)存中讀取原始數(shù)據(jù),上面的示例中已包含此代碼
2.類CodecQueue:用于將解碼數(shù)據(jù)存入隊(duì)列中,并通過單獨(dú)的線程進(jìn)行編碼
class AVFrameQueue { public: AVFrameQueue() = default; ~AVFrameQueue() {} void push(AVFrame** frame) { std::unique_lock<std::mutex> lck(mtx); queue.push(*frame); cv.notify_all(); } void pop(AVFrame** frame) { std::unique_lock<std::mutex> lck(mtx); while (queue.empty()) { //cv.wait(lck); if (cv.wait_for(lck, std::chrono::milliseconds(150)) == std::cv_status::timeout) { fprintf(stderr, "#### Warning: wait timeout\n"); *frame = nullptr; return; } } *frame = queue.front(); queue.pop(); } size_t size() const { return queue.size(); } private: std::queue<AVFrame*> queue; std::mutex mtx; std::condition_variable cv; }; class CodecQueue { public: CodecQueue() = default; void init(unsigned int frame_num) { for (auto i = 0; i < frame_num; ++i) { AVFrame* frame = nullptr; pushDecode(&frame); } } ~CodecQueue() { release(); } void release() { AVFrame* frame = nullptr; while (getDecodeSize() > 0) { popDecode(&frame); av_frame_free(&frame); } while (getEncodeSize() > 0) { popEncode(&frame); av_frame_free(&frame); } } void pushDecode(AVFrame** frame) { decode_queue.push(frame); } void popDecode(AVFrame** frame) { decode_queue.pop(frame); } size_t getDecodeSize() const { return decode_queue.size(); } void pushEncode(AVFrame** frame) { encode_queue.push(frame); } void popEncode(AVFrame** frame) { encode_queue.pop(frame); } size_t getEncodeSize() const { return encode_queue.size(); } private: AVFrameQueue decode_queue, encode_queue; };
3.類VideoCodec:供外面的接口調(diào)用,封裝了視頻的解碼和編碼過程
聲明如下:
typedef struct CodecCtx { char outfile_name[VIDEO_CODEC_MAX_STRING_SIZE]; char video_size[VIDEO_CODEC_MAX_STRING_SIZE]; char bitrate_str[VIDEO_CODEC_MAX_STRING_SIZE]; char pixel_format[VIDEO_CODEC_MAX_STRING_SIZE]; char filter_descr[VIDEO_CODEC_MAX_STRING_SIZE]; AVFormatContext* ifmt_ctx; AVFormatContext* ofmt_ctx; AVCodecContext* dec_ctx; AVCodecContext* enc_ctx; AVFrame* dec_frame; AVFilterContext* buffersink_ctx; AVFilterContext* buffersrc_ctx; AVFilterGraph* filter_graph; AVPacket* enc_pkt; AVRational frame_rate; int term_status; int stream_index; int frame_count; bool encode_thread_end; } CodecCtx; class VideoCodec { public: VideoCodec() = default; ~VideoCodec() { } void setOutfileName(const std::string& name) { outfile_name_ = name; } void setVideoSize(const std::string& size) { video_size_ = size; } void setPixelFormat(const std::string& format) { pixel_format_ = format; } void setFilterDescr(const std::string& filter_descr) { filter_descr_ = filter_descr; } void stopEncode() { while (raw_packet_queue_.getScaleSize() != 0); codec_ctx_->term_status = 1; Buffer buffer; raw_packet_queue_.popPacket(buffer); memset(buffer.data, 0, block_size_); raw_packet_queue_.pushScale(buffer); // for av_read_frame to exit normally } PacketScaleQueue& get_raw_packet_queue(unsigned int buffer_num, size_t buffer_size) { raw_packet_queue_.init(buffer_num, buffer_size); block_size_ = buffer_size; return raw_packet_queue_; } int openEncode(); int processEncode(); int closeEncode(); private: std::string outfile_name_ = ""; std::string video_size_ = ""; std::string pixel_format_ = ""; std::string filter_descr_ = ""; PacketScaleQueue raw_packet_queue_; int block_size_ = 0; CodecCtx* codec_ctx_ = nullptr; AVIOContext* avio_ctx_ = nullptr; CodecQueue codec_queue_; std::thread encode_thread_; int get_decode_context(); int get_encode_context(); int init_filters(); int filter_encode_write_frame(AVFrame* frame); int get_output_format_context(); int flush_encode_write_frame(); int flush_decoder(); int flush_encoder(); void flush_codec(); };
類VideoCodec實(shí)現(xiàn)部分:是之前示例的整理,參考之前示例
4.測(cè)試代碼,即調(diào)用VideoCodec接口,以下是同時(shí)兩個(gè)線程進(jìn)行編碼寫
namespace { const int total_push_count = 121; bool flag1 = true; const size_t block_size_1 = 640 * 480 * 3; size_t total_push_count_1 = 0; void fill_raw_data_1(PacketScaleQueue& raw_packet) { unsigned char value = 0; while (total_push_count_1 < total_push_count) { value += 10; if (value >= 255) value = 0; Buffer buffer; raw_packet.popPacket(buffer); memset(buffer.data, value, block_size_1); raw_packet.pushScale(buffer); std::this_thread::sleep_for(std::chrono::milliseconds(33)); ++total_push_count_1; } flag1 = false; } void sleep_seconds_1(VideoCodec& video_codec) { while (flag1) { std::this_thread::sleep_for(std::chrono::milliseconds(33)); } video_codec.stopEncode(); } void encode_1() { VideoCodec video_codec; video_codec.setOutfileName("out1.mp4"); video_codec.setVideoSize("640x480"); video_codec.setPixelFormat("bgr24"); video_codec.setFilterDescr("movie=1.jpg[logo];[in][logo]overlay=10:20[out]"); auto& raw_queue = video_codec.get_raw_packet_queue(16, block_size_1); std::thread thread_fill(fill_raw_data_1, std::ref(raw_queue)); auto ret = video_codec.openEncode(); if (ret != 0) { std::cout << "fail to openEncode: " << ret << std::endl; //return -1; } std::thread thread_sleep(sleep_seconds_1, std::ref(video_codec)); ret = video_codec.processEncode(); if (ret != 0) { std::cout << "fail to processEncode: " << ret << std::endl; //return -1; } thread_fill.join(); thread_sleep.join(); video_codec.closeEncode(); std::cout << "1 total push count: " << total_push_count_1 << std::endl; } bool flag2 = true; const size_t block_size_2 = 640 * 480 * 3; size_t total_push_count_2 = 0; void fill_raw_data_2(PacketScaleQueue& raw_packet) { unsigned char value = 0; while (total_push_count_2 < total_push_count) { value += 10; if (value >= 255) value = 0; Buffer buffer; raw_packet.popPacket(buffer); memset(buffer.data, value, block_size_2); raw_packet.pushScale(buffer); std::this_thread::sleep_for(std::chrono::milliseconds(33)); ++total_push_count_2; } flag2 = false; } void sleep_seconds_2(VideoCodec& video_codec) { while (flag2) { std::this_thread::sleep_for(std::chrono::milliseconds(33)); } video_codec.stopEncode(); } void encode_2() { VideoCodec video_codec; video_codec.setOutfileName("out2.mp4"); video_codec.setVideoSize("640x480"); video_codec.setPixelFormat("bgr24"); video_codec.setFilterDescr("movie=1.jpg[logo];[in][logo]overlay=10:20[out]"); auto& raw_queue = video_codec.get_raw_packet_queue(16, block_size_2); std::thread thread_fill(fill_raw_data_2, std::ref(raw_queue)); auto ret = video_codec.openEncode(); if (ret != 0) { std::cout << "fail to openEncode: " << ret << std::endl; //return -1; } std::thread thread_sleep(sleep_seconds_2, std::ref(video_codec)); ret = video_codec.processEncode(); if (ret != 0) { std::cout << "fail to processEncode: " << ret << std::endl; //return -1; } thread_fill.join(); thread_sleep.join(); std::cout << "2 total push count: " << total_push_count_2 << std::endl; } } // namespce int test_ffmpeg_libavfilter_movie_multi_thread() { std::thread thread_1(encode_1); std::thread thread_2(encode_2); thread_1.join(); thread_2.join(); std::cout << "test finish" << std::endl; return 0; }
生成的mp4文件結(jié)果如下:在release下生成的兩個(gè)視頻文件完全一致;在debug下編碼過程中有時(shí)會(huì)timeout
GitHub:https://github.com/fengbingchun/OpenCV_Test
到此這篇關(guān)于FFmpeg實(shí)現(xiàn)多線程編碼并保存mp4文件的文章就介紹到這了,更多相關(guān)FFmpeg多線程編碼內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++11 強(qiáng)類型枚舉相關(guān)總結(jié)
這篇文章主要介紹了C++11 強(qiáng)類型枚舉的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)使用c++11,感興趣的朋友可以了解下2021-02-02C++實(shí)現(xiàn)LeetCode(642.設(shè)計(jì)搜索自動(dòng)補(bǔ)全系統(tǒng))
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(642.設(shè)計(jì)搜索自動(dòng)補(bǔ)全系統(tǒng)),本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08淺談Windows系統(tǒng)下C語言編程中Glib庫的使用
這篇文章主要介紹了Windows系統(tǒng)下C語言編程中Glib庫的使用,Glib庫在多線程編程中經(jīng)??梢杂玫?需要的朋友可以參考下2016-02-02C++時(shí)間戳轉(zhuǎn)換成日期時(shí)間的步驟和示例代碼
這篇文章主要介紹了C++時(shí)間戳轉(zhuǎn)換成日期時(shí)間的步驟和示例代碼,需要的朋友可以參考下2016-12-12C++中關(guān)于=default和=delete問題
這篇文章主要介紹了C++中關(guān)于=default和=delete問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07