音视频开发10. 使用ffmpeg 流媒体视频流截图jpg实践
Posted 编程圈子
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了音视频开发10. 使用ffmpeg 流媒体视频流截图jpg实践相关的知识,希望对你有一定的参考价值。
音视频开发10. 使用ffmpeg 流媒体视频流截图jpg实践
一、准备环境
- CentOS 安装ffmpeg开发库
- 可播放的视频流媒体地址
- 安装好C++编译环境,cmake/g++等
二、准备知识
1. RGB
使用红绿蓝来表示颜色,较为常见,这里不作太多解释。
2. YUV
YUV也是一种颜色编码方法,主要用在电视系统和模拟视频领域,3个分量分表表示:
- Y:明亮度(Luminance或Luma),也就是灰度;
- U,V: 表示色度(Chrominance或Chroma),描述影像色彩及饱和度,用于指定像素的颜色。
YUV把亮度与色彩信息分离,如果没有UV信息图像就是黑白的,最初便于解决彩色电视机与黑白电视的兼容问题。
YUV格式占用的空间比RGB少,比如:
- RGB24一帧的大小=宽×高×3 Byte
- RGB32一帧的大小=宽×高×4 Byte
- YUV420一帧的大小=宽×高×1.5 Byte
3. FFmpeg解码时的视频格式
FFmpeg视频解码后帧格式一般是:AV_PIX_FMT_YUV420P,数据结构是AVFrame,其中的data[]数组存放YUV数据:
- data[0]——-Y分量
- data[1]——-U分量
- data[2]——-V分量
在linesize[]数组中保存对应通道的数据宽度 : - linesize[0]——-Y分量的宽度
- linesize[1]——-U分量的宽度
- linesize[2]——-V分量的宽度
4. YUV转RGB代码示例
通过下面示例可以更清楚了解AVFrame中存放的YUV数据格式。
uint8_t *AVFrame2Img(AVFrame *pFrame)
int frameHeight = pFrame->height;
int frameWidth = pFrame->width;
int channels = 3;
//反转图像
pFrame->data[0] += pFrame->linesize[0] * (frameHeight - 1);
pFrame->linesize[0] *= -1;
pFrame->data[1] += pFrame->linesize[1] * (frameHeight / 2 - 1);
pFrame->linesize[1] *= -1;
pFrame->data[2] += pFrame->linesize[2] * (frameHeight / 2 - 1);
pFrame->linesize[2] *= -1;
//创建保存yuv数据的buffer
uint8_t *pDecodedBuffer = (uint8_t *) malloc(
frameHeight * frameWidth * sizeof(uint8_t) * channels);
//从AVFrame中获取yuv420p数据,并保存到buffer
int i, j, k;
//拷贝y分量
for (i = 0; i < frameHeight; i++)
memcpy(pDecodedBuffer + frameWidth * i,
pFrame->data[0] + pFrame->linesize[0] * i,
frameWidth);
//拷贝u分量
for (j = 0; j < frameHeight / 2; j++)
memcpy(pDecodedBuffer + frameWidth * i + frameWidth / 2 * j,
pFrame->data[1] + pFrame->linesize[1] * j,
frameWidth / 2);
//拷贝v分量
for (k = 0; k < frameHeight / 2; k++)
memcpy(pDecodedBuffer + frameWidth * i + frameWidth / 2 * j + frameWidth / 2 * k,
pFrame->data[2] + pFrame->linesize[2] * k,
frameWidth / 2);
return pDecodedBuffer;
使用ffmpeg的 sws_scale 可以实现格式转换:
sws_scale(img_convert_ctx, pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize);
三、流程
1. 整体流程
- 打开输入流
- 找到视频流信息
- 创建解码器
- 创建图像转换上下文 SwsContext img_convert_ctx
- 分配 AVPacket
- 读取流
- 发送到解码器
- 读取解码结果
- 图片类型YUV转换为RGB
- 调用保存jpeg函数
- 释放资源
2. 保存图片过程
- 创建 AVFormatContext上下文
- 构建 AVFrame
- 创建编码器
- 复制编码器参数
- 写入jpeg头
- 创建 AVPacket
- 解码
- 得到编码数据
- 写入一帧
- 释放资源
四、实现
1. CMakeLists.txt
cmake_minimum_required(VERSION 3.17)
project(ffmpeg_demo)
# 设置ffmpeg依赖库及头文件所在目录,并存进指定变量
set(ffmpeg_libs_DIR /home/xundh/ffmpeg_sources/ffmpeg-4.2.2)
set(ffmpeg_headers_DIR /home/xundh/ffmpeg_sources/ffmpeg-4.2.2)
#对于find_package找不到的外部依赖库,可以用add_library添加
# SHARED表示添加的是动态库
# IMPORTED表示是引入已经存在的动态库
add_library( avcodec SHARED IMPORTED)
add_library( avfilter SHARED IMPORTED )
add_library( swresample SHARED IMPORTED )
add_library( swscale SHARED IMPORTED )
add_library( avformat SHARED IMPORTED )
add_library( avutil SHARED IMPORTED )
#指定所添加依赖库的导入路径
set_target_properties( avcodec PROPERTIES IMPORTED_LOCATION $ffmpeg_libs_DIR/libavcodec/libavcodec.so )
set_target_properties( avfilter PROPERTIES IMPORTED_LOCATION $ffmpeg_libs_DIR/libavfilter/libavfilter.so )
set_target_properties( swresample PROPERTIES IMPORTED_LOCATION $ffmpeg_libs_DIR/libswresample/libswresample.so )
set_target_properties( swscale PROPERTIES IMPORTED_LOCATION $ffmpeg_libs_DIR/libswscale/libswscale.so )
set_target_properties( avformat PROPERTIES IMPORTED_LOCATION $ffmpeg_libs_DIR/libavformat/libavformat.so )
set_target_properties( avutil PROPERTIES IMPORTED_LOCATION $ffmpeg_libs_DIR/libavutil/libavutil.so )
# 添加头文件路径到编译器的头文件搜索路径下,多个路径以空格分隔
include_directories( $ffmpeg_headers_DIR )
link_directories($ffmpeg_libs_DIR )
link_directories(/usr/lib)
set(CMAKE_CXX_STANDARD 14)
add_executable(ffmpeg_demo main.cpp)
target_link_libraries($PROJECT_NAME avcodec avformat avutil swresample swscale swscale avfilter )
2. main.cpp
#include <stdio.h>
#include <iostream>
#ifdef __cplusplus
extern "C"
#endif
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/avutil.h"
#include "libavutil/log.h"
#include "libswscale/swscale.h"
#ifdef __cplusplus
#endif
using namespace std;
#define CAPTURE_COUNT 1
/**
*
* ffmpeg -i "rtsp://admin123:tang3shan@112.31.211.240:554/cam/realmonitor?channel=7&subtype=1" -y -f image2 -ss 00:00:03 -vframes 1 -s 640x360 1.jpg
*/
// 回调函数的参数,时间和有无流的判断
typedef struct
time_t lasttime;
bool connected;
Runner;
// 回调函数
int interrupt_callback(void *p)
Runner *r = (Runner *)p;
if (r->lasttime > 0)
if (time(NULL) - r->lasttime > 10 && !r->connected)
// 等待超过1s则中断
return 1;
return 0;
/**
* 写入YUV的灰度图片
*/
void save_gray_frame(unsigned char *buf, int wrap, int xsize, int ysize, char *filename)
FILE *grayFile;
int i;
grayFile = fopen(filename, "w");
// 写入一个pgm文件最小头部,便携灰度图格式: https://en.wikipedia.org/wiki/Netpbm_format#PGM_example
fprintf(grayFile, "P5\\n%d %d\\n%d\\n", xsize, ysize, 255);
// 逐行写入
for (i = 0; i < ysize; i++)
fwrite(buf + i * wrap, 1, xsize, grayFile);
// 关闭文件
fclose(grayFile);
/**
* 将AVFrame(YUV420格式)保存为JPEG格式的图片
*/
int savePicture(AVFrame *pFrame, char *out_name)
//编码保存图片
int width = pFrame->width;
int height = pFrame->height;
AVCodecContext *pAVCodecContext = NULL;
AVFormatContext *pAVFormatContext = avformat_alloc_context();
// 设置输出文件格式
pAVFormatContext->oformat = av_guess_format("mjpeg", NULL, NULL);
cout << "打开文件" << out_name << endl;
// 创建并初始化输出 AVIOContext
int ret1 = avio_open(&pAVFormatContext->pb, out_name, AVIO_FLAG_READ_WRITE);
if (ret1 < 0)
cout << "打开输出文件失败, errorCode" << ret1 << endl;
return -1;
// 构建一个新stream
AVStream *pAVStream = avformat_new_stream(pAVFormatContext, 0);
if (pAVStream == NULL)
return -1;
AVCodecParameters *parameters = pAVStream->codecpar;
parameters->codec_id = pAVFormatContext->oformat->video_codec;
parameters->codec_type = AVMEDIA_TYPE_VIDEO;
parameters->format = AV_PIX_FMT_YUVJ420P;
parameters->width = pFrame->width;
parameters->height = pFrame->height;
AVCodec *pCodec = avcodec_find_encoder(pAVStream->codecpar->codec_id);
if (!pCodec)
cout << "找不到jpeg编码器" << endl;
return -1;
pAVCodecContext = avcodec_alloc_context3(pCodec);
if (!pAVCodecContext)
cout << "获取编码器上下文失败" << endl;
exit(1);
if ((avcodec_parameters_to_context(pAVCodecContext, pAVStream->codecpar)) < 0)
cout << "复制编码器参数发生错误" << endl;
return -1;
pAVCodecContext->time_base = (AVRational)1, 25;
if (avcodec_open2(pAVCodecContext, pCodec, NULL) < 0)
cout << "打开编码器上下文失败" << endl;
return -1;
int ret = avformat_write_header(pAVFormatContext, NULL);
if (ret < 0)
cout << "写入图片头失败" << endl;
return -1;
int y_size = width * height;
// Encode
// 给AVPacket分配足够大的空间
AVPacket pkt;
av_new_packet(&pkt, y_size * 3);
// 解码数据
ret = avcodec_send_frame(pAVCodecContext, pFrame);
if (ret < 0)
cout << "解码失败" << endl;
return -1;
// 得到编码后数据
ret = avcodec_receive_packet(pAVCodecContext, &pkt);
if (ret < 0)
cout << "编码失败" << endl;
return -1;
ret = av_write_frame(pAVFormatContext, &pkt);
if (ret < 0)
cout << "写入帧失败" << endl;
return -1;
av_packet_unref(&pkt);
// 写入索引
av_write_trailer(pAVFormatContext);
// 释放资源
avcodec_close(pAVCodecContext);
avio_close(pAVFormatContext->pb);
avformat_free_context(pAVFormatContext);
return 0;
int main(int argc, char *argv[])
AVFormatContext *pAVFormatContext;
AVCodecContext *pAVCodecContext;
AVCodec *pAVCodec;
AVFrame *pAVFrame, *pAVFrameRGB;
AVPacket *pAVPacket;
uint8_t *out_buffer;
static struct SwsContext *img_convert_ctx;
int videoStream, i, numBytes;
int ret, got_picture;
// 注册ffmpeg
av_register_all();
avformat_network_init();
// AVFormatContext 分配内存
pAVFormatContext = avformat_alloc_context();
/// 推流参数设置
AVDictionary *avdic = NULL;
char option_key[] = "rtsp_transport";
char option_value[] = "tcp";
av_dict_set(&avdic, option_key, option_value, 0);
char option_key2[] = "max_delay";
char option_value2[] = "100";
av_dict_set(&avdic, option_key2, option_value2, 0);
// 视频地址
char url[] = "http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear2/prog_index.m3u8";
Runner input_runner = 0;
pAVFormatContext->interrupt_callback.callback = interrupt_callback;
pAVFormatContext->interrupt_callback.opaque = &input_runner;
input_runner.lasttime = time(NULL);
input_runner.connected = false;
int avformat_ret = avformat_open_input(&pAVFormatContext, url, NULL, &avdic);
if (avformat_ret != 0)
cout << "无法打开文件,返回值:" << avformat_ret << endl;
return 1;
if (avformat_find_stream_info(pAVFormatContext, NULL) < 0)
cout << "无法打开文件流" << endl;
return 1;
videoStream = -1;
///循环查找视频中包含的流信息,直到找到视频类型的流,保存到videoStream变量中
for (i = 0; i < pAVFormatContext->nb_streams; i++)
if (pAVFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
videoStream = i;
///如果videoStream为-1 说明没有找到视频流
if (videoStream == -1)
cout << "没有找到视频流" << endl;
return -1;
cout << "查找解码器" << endl;;
pAVCodecContext = pAVFormatContext->streams[videoStream]->codec;
pAVCodec = avcodec_find_decoder(pAVCodecContext->codec_id);
pAVCodecContext->bit_rate = 0; //初始化为0
pAVCodecContext->time_base.num = 1; //下面两行:一秒钟25帧
pAVCodecContext->time_base.den = 10;
pAVCodecContext->frame_number = 1; //每包一个视频帧
if (pAVCodec == NULL)
cout << "查找解码器失败"<< endl;
return 1;
cout << "打开解码器" << endl;;
///打开解码器
if (avcodec_open2(pAVCodecContext, pAVCodec, NULL) < 0)
cout << "打开解码器失败" << endl;
return 1;
pAVFrame = av_frame_alloc();
pAVFrameRGB = av_frame_alloc();
///转换帧格式,这里将解码后的YUV数据通过转换成RGB32
img_convert_ctx = sws_getContext(pAVCodecContext->width, pAVCodecContext->height,
pAVCodecContext->pix_fmt, pAVCodecContext->width, pAVCodecContext->height,
AV_PIX_FMT_RGB32, SWS_BICUBIC, NULL, NULL, NULL);
numBytes = avpicture_get_size(AV_PIX_FMT_RGB32, pAVCodecContext->width, pAVCodecContext->height);
out_buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t));
avpicture_fill((AVPicture *)pAVFrameRGB, out_buffer, AV_PIX_FMT_RGB32,
pAVCodecContext->width, pAVCodecContext->height);
int y_size = pAVCodecContext->width * pAVCodecContext->height;
pAVPacket = (AVPacket *)malloc(sizeof(AVPacket)); //分配一个packet
cout<< "打印流信息" << endl;
av_dump_format(pAVFormatContext, 0, url, 0);
av_new_packet(pAVPacket, y_size); //分配packet的数据
i = 0;
const char *in_filename;
char buf[1024];
int frame_count = 0;
while (av_read_frame(pAVFormatContext, pAVPacket) >= 0)
if (pAVPacket->stream_index == videoStream以上是关于音视频开发10. 使用ffmpeg 流媒体视频流截图jpg实践的主要内容,如果未能解决你的问题,请参考以下文章