C++?ffmpeg實(shí)現(xiàn)將視頻幀轉(zhuǎn)換成jpg或png等圖片
前言
有時(shí)播放實(shí)時(shí)流的時(shí)候有截圖的需求,需要將解碼出來(lái)的圖片保存本地或上傳服務(wù)器,這時(shí)就需要將avframe中的數(shù)據(jù)編碼成png、jpg等格式的圖片,我們使用ffmpeg的相關(guān)編碼器就可以實(shí)現(xiàn)功能。
一、如何實(shí)現(xiàn)
1、查找編碼器
首先需要查找圖片編碼器,比如jpg為AV_CODEC_ID_MJPEG,png為AV_CODEC_ID_PNG
示例代碼:
enum AVCodecID codec = avcodec_find_encoder(AV_CODEC_ID_MJPEG);
2、構(gòu)造編碼器上下文
有了編碼器就可以構(gòu)造編碼器上下文了。
AVCodecContext*ctx = avcodec_alloc_context3(codec); ctx->bit_rate = 3000000; ctx->width = frame->width;//視頻幀的寬 ctx->height = frame->height;//視頻幀的高 ctx->time_base.num = 1; ctx->time_base.den = 25; ctx->gop_size = 10; ctx->max_b_frames = 0; ctx->thread_count = 1; ctx->pix_fmt = *codec->pix_fmts;//使用編碼器適配的像素格式 //打開編碼器 avcodec_open2(ctx, codec, NULL);
3、像素格式轉(zhuǎn)換
如果輸入視頻幀的像素和編碼器的像素格式不相同則需要轉(zhuǎn)換像素格式,我們采用SwsContext 轉(zhuǎn)換即可
AVFrame*rgbFrame = av_frame_alloc();//轉(zhuǎn)換后的幀 swsContext = sws_getContext(frame->width, frame->height, (enum AVPixelFormat)frame->format, frame->width, frame->height, ctx->pix_fmt, 1, NULL, NULL, NULL); int bufferSize = av_image_get_buffer_size(ctx->pix_fmt, frame->width, frame->height, 1) * 2; buffer = (unsigned char*)av_malloc(bufferSize); //構(gòu)造幀的緩存 av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, buffer, ctx->pix_fmt, frame->width, frame->height, 1); sws_scale(swsContext, frame->data, frame->linesize, 0, frame->height, rgbFrame->data, rgbFrame->linesize); //構(gòu)造必要的參數(shù) rgbFrame->format = ctx->pix_fmt; rgbFrame->width = ctx->width; rgbFrame->height = ctx->height;
4、編碼
得到轉(zhuǎn)后的幀就可以編碼
ret = avcodec_send_frame(ctx, rgbFrame);
5、獲取圖片數(shù)據(jù)
獲取解碼后的包即可得到圖片數(shù)據(jù)。
uint8_t* outbuf;//輸出圖片的緩存 size_t outbufSize;//緩存大小 AVPacket pkt; av_init_packet(&pkt); //獲取解碼的包 avcodec_receive_packet(ctx, &pkt); //將圖片數(shù)據(jù)拷貝到緩存 if (pkt.size > 0 && pkt.size <= outbufSize) memcpy(outbuf, pkt.data, pkt.size);
6、銷毀資源
將上述步驟使用的對(duì)象銷毀。
if (swsContext)
{
sws_freeContext(swsContext);
}
if (rgbFrame)
{
av_frame_unref(rgbFrame);
av_frame_free(&rgbFrame);
}
if (buffer)
{
av_free(buffer);
}
av_packet_unref(&pkt);
if (ctx)
{
avcodec_close(ctx);
avcodec_free_context(&ctx);
}二、完整代碼
/// <summary>
/// 幀轉(zhuǎn)圖片
/// 如果外部提供的緩存長(zhǎng)度不足則不會(huì)寫入。
/// </summary>
/// <param name="frame">[in]視頻幀</param>
/// <param name="codecID">[in]圖片編碼器ID,如jpg:AV_CODEC_ID_MJPEG,png:AV_CODEC_ID_PNG</param>
/// <param name="outbuf">[out]圖片緩存,由外部提供</param>
/// <param name="outbufSize">[in]圖片緩存長(zhǎng)度</param>
/// <returns>返回圖片實(shí)際長(zhǎng)度</returns>
static int frameToImage(AVFrame* frame, enum AVCodecID codecID, uint8_t* outbuf, size_t outbufSize)
{
int ret = 0;
AVPacket pkt;
AVCodec* codec;
AVCodecContext* ctx = NULL;
AVFrame* rgbFrame = NULL;
uint8_t* buffer = NULL;
struct SwsContext* swsContext = NULL;
av_init_packet(&pkt);
codec = avcodec_find_encoder(codecID);
if (!codec)
{
printf("avcodec_send_frame error %d", codecID);
goto end;
}
if (!codec->pix_fmts)
{
printf("unsupport pix format with codec %s", codec->name);
goto end;
}
ctx = avcodec_alloc_context3(codec);
ctx->bit_rate = 3000000;
ctx->width = frame->width;
ctx->height = frame->height;
ctx->time_base.num = 1;
ctx->time_base.den = 25;
ctx->gop_size = 10;
ctx->max_b_frames = 0;
ctx->thread_count = 1;
ctx->pix_fmt = *codec->pix_fmts;
ret = avcodec_open2(ctx, codec, NULL);
if (ret < 0)
{
printf("avcodec_open2 error %d", ret);
goto end;
}
if (frame->format != ctx->pix_fmt)
{
rgbFrame = av_frame_alloc();
if (rgbFrame == NULL)
{
printf("av_frame_alloc fail:%d");
goto end;
}
swsContext = sws_getContext(frame->width, frame->height, (enum AVPixelFormat)frame->format, frame->width, frame->height, ctx->pix_fmt, 1, NULL, NULL, NULL);
if (!swsContext)
{
printf("sws_getContext fail:%d");
goto end;
}
int bufferSize = av_image_get_buffer_size(ctx->pix_fmt, frame->width, frame->height, 1) * 2;
buffer = (unsigned char*)av_malloc(bufferSize);
if (buffer == NULL)
{
printf("buffer alloc fail:%d", bufferSize);
goto end;
}
av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, buffer, ctx->pix_fmt, frame->width, frame->height, 1);
if ((ret = sws_scale(swsContext, frame->data, frame->linesize, 0, frame->height, rgbFrame->data, rgbFrame->linesize)) < 0)
{
printf("sws_scale error %d", ret);
}
rgbFrame->format = ctx->pix_fmt;
rgbFrame->width = ctx->width;
rgbFrame->height = ctx->height;
ret = avcodec_send_frame(ctx, rgbFrame);
}
else
{
ret = avcodec_send_frame(ctx, frame);
}
if (ret < 0)
{
printf("avcodec_send_frame error %d", ret);
goto end;
}
ret = avcodec_receive_packet(ctx, &pkt);
if (ret < 0)
{
printf("avcodec_receive_packet error %d", ret);
goto end;
}
if (pkt.size > 0 && pkt.size <= outbufSize)
memcpy(outbuf, pkt.data, pkt.size);
ret = pkt.size;
end:
if (swsContext)
{
sws_freeContext(swsContext);
}
if (rgbFrame)
{
av_frame_unref(rgbFrame);
av_frame_free(&rgbFrame);
}
if (buffer)
{
av_free(buffer);
}
av_packet_unref(&pkt);
if (ctx)
{
avcodec_close(ctx);
avcodec_free_context(&ctx);
}
return ret;
}三、使用示例
1、截取視頻幀并保存文件
void main() {
AVFrame* frame;//視頻解碼得到的幀
saveFrameToJpg(frame,"snapshot.jpg");
}
/// <summary>
/// 將視頻幀保存為jpg圖片
/// </summary>
/// <param name="frame">視頻幀</param>
/// <param name="path">保存的路徑</param>
void saveFrameToJpg(AVFrame*frame,const char*path) {
//確保緩沖區(qū)長(zhǎng)度大于圖片,使用brga像素格式計(jì)算。如果是bmp或tiff依然可能超出長(zhǎng)度,需要加一個(gè)頭部長(zhǎng)度,或直接乘以2。
int bufSize = av_image_get_buffer_size(AV_PIX_FMT_BGRA, frame->width, frame->height, 64);
//申請(qǐng)緩沖區(qū)
uint8_t* buf = (uint8_t*)av_malloc(bufSize);
//將視頻幀轉(zhuǎn)換成圖片
int picSize = frameToImage(frame, AV_CODEC_ID_MJPEG, buf, bufSize);
//寫入文件
auto f = fopen(path, "wb+");
if (f)
{
fwrite(buf, sizeof(uint8_t), bufSize, f);
fclose(f);
}
//釋放緩沖區(qū)
av_free(buf);
}2、自定義數(shù)據(jù)構(gòu)造AVFrame
void main() {
uint8_t*frameData;//解碼得到的視頻數(shù)據(jù)
AVFrame* frame=allocFrame(frameData,640,360,AV_PIX_FMT_YUV420P);
saveFrameToJpg(frame,"snapshot.jpg");//此方法定義在示例1中
av_frame_free(&frame);
}
/// <summary>
/// 通過(guò)裸數(shù)據(jù)生成avframe
/// </summary>
/// <param name="frameData">幀數(shù)據(jù)</param>
/// <param name="width">幀寬</param>
/// <param name="height">幀高</param>
/// <param name="format">像素格式</param>
/// <returns>avframe,使用完成后需要調(diào)用av_frame_free釋放</returns>
AVFrame* allocFrame(uint8_t*frameData,int width,int height,AVPixelFormat format) {
AVFrame* frame = av_frame_alloc();
frame->width = width;
frame->height = height;
frame->format = format;
av_image_fill_arrays(frame->data, frame->linesize, frameData, format, frame->width, frame->height, 64);
return frame;
}總結(jié)
以上就是今天要講的內(nèi)容,總的來(lái)說(shuō)整個(gè)流程和一般的視頻編碼是一致的,只是選擇的編碼器不同,拿到的圖片數(shù)據(jù)在內(nèi)存中,可以直接網(wǎng)絡(luò)傳輸或保存到本地??梢院芊奖愕脑谝曨l界面過(guò)程中截圖,尤其是解碼使用ffmpeg的情況下。實(shí)現(xiàn)也不算難,寫成文章是為了以后能直接復(fù)用,畢竟時(shí)間久了一些細(xì)節(jié)還是會(huì)遺忘的。
到此這篇關(guān)于C++ ffmpeg實(shí)現(xiàn)將視頻幀轉(zhuǎn)換成jpg或png等圖片的文章就介紹到這了,更多相關(guān)C++ ffmpeg視頻幀轉(zhuǎn)圖片內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
CString,字符串,整數(shù)等相互轉(zhuǎn)換方法(推薦)
下面小編就為大家?guī)?lái)一篇CString,字符串,整數(shù)等相互轉(zhuǎn)換方法(推薦)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-09-09
C++解決輸出鏈表中倒數(shù)k個(gè)結(jié)點(diǎn)的問(wèn)題
這篇文章主要給大家介紹了關(guān)于如何利用C++解決輸出鏈表中倒數(shù)k個(gè)結(jié)點(diǎn)的問(wèn)題,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用C++具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2021-12-12
C語(yǔ)言實(shí)現(xiàn)任意進(jìn)制轉(zhuǎn)換器
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)任意進(jìn)制轉(zhuǎn)換器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01
Qt與Web混合開發(fā)實(shí)現(xiàn)雙向通信的示例
本文主要介紹了Qt與Web混合開發(fā)實(shí)現(xiàn)雙向通信的示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07
Qt重寫QStackedWidget模擬實(shí)現(xiàn)home界面滑動(dòng)效果
這篇文章主要為大家詳細(xì)介紹了Qt如何通過(guò)重寫QStackedWidget模擬實(shí)現(xiàn)home界面滑動(dòng)效果,文中的實(shí)現(xiàn)過(guò)程講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2022-11-11
C++ Boost.Range與Adapters庫(kù)使用詳解
這篇文章主要介紹了C++ Boost.Range與Adapters庫(kù)使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-11-11

