音视频开发10. 使用ffmpeg 流媒体视频流截图jpg实践
音视频开发10. 使用ffmpeg 流媒体视频流截图jpg实践
- 一、准备环境
- 二、准备知识
- 1. RGB
- 2. YUV
- 3. FFmpeg解码时的视频格式
- 4. YUV转RGB代码示例
- 三、流程
- 1. 整体流程
- 2. 保存图片过程
- 四、实现
- 1. CMakeLists.txt
- 2. main.cpp
一、准备环境
- 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数据的bufferuint8_t *pDecodedBuffer = (uint8_t *) malloc(frameHeight * frameWidth * sizeof(uint8_t) * channels);//从AVFrame中获取yuv420p数据,并保存到bufferint 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文件最小头部,便携灰度图格式: (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;// 创建并初始化输出 AVIOContextint ret1 = avio_open(&pAVFormatContext->pb, out_name, AVIO_FLAG_READ_WRITE);if (ret1 < 0){cout << "打开输出文件失败, errorCode" << ret1 << endl;return -1;}// 构建一个新streamAVStream *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;// 注册ffmpegav_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[] = ".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; //初始化为0pAVCodecContext->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数据通过转换成RGB32img_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)); //分配一个packetcout<< "打印流信息" << 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){ret = avcodec_send_packet(pAVCodecContext, pAVPacket);if (ret < 0){av_packet_unref(pAVPacket);continue;}do{// 读取帧ret = avcodec_receive_frame(pAVCodecContext, pAVFrame);if (ret < 0)break;else if (ret == 0){ /* Got a frame successfully */sws_scale(img_convert_ctx, pAVFrame->data, pAVFrame->linesize, 0, pAVCodecContext->height, pAVFrameRGB->data, pAVFrameRGB->linesize);// 注释的这一段程序用来存储YUV的 Y 通道灰度图片// char frame_filename[1024];// snprintf(frame_filename, sizeof(frame_filename), "%s-%d.pgm", "frame", pCodecCtx->frame_number);// save_gray_frame(pFrame->data[0], pFrame->linesize[0], pFrame->width, pFrame->height, frame_filename);snprintf(buf, sizeof(buf), "picture-%d.jpg", frame_count);savePicture(pAVFrame, buf);frame_count++;}else if (ret == AVERROR_EOF){avcodec_flush_buffers(pAVCodecContext);break;}} while (ret != AVERROR(EAGAIN));av_packet_unref(pAVPacket);// 截取几张if (frame_count >= CAPTURE_COUNT)break;}else{av_packet_unref(pAVPacket);continue;}av_free_packet(pAVPacket); //释放资源,否则内存会一直上升}av_free(out_buffer);av_free(pAVFrameRGB);avcodec_close(pAVCodecContext);avformat_close_input(&pAVFormatContext);return 0;
}
音视频开发10. 使用ffmpeg 流媒体视频流截图jpg实践
音视频开发10. 使用ffmpeg 流媒体视频流截图jpg实践
- 一、准备环境
- 二、准备知识
- 1. RGB
- 2. YUV
- 3. FFmpeg解码时的视频格式
- 4. YUV转RGB代码示例
- 三、流程
- 1. 整体流程
- 2. 保存图片过程
- 四、实现
- 1. CMakeLists.txt
- 2. main.cpp
一、准备环境
- 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数据的bufferuint8_t *pDecodedBuffer = (uint8_t *) malloc(frameHeight * frameWidth * sizeof(uint8_t) * channels);//从AVFrame中获取yuv420p数据,并保存到bufferint 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文件最小头部,便携灰度图格式: (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;// 创建并初始化输出 AVIOContextint ret1 = avio_open(&pAVFormatContext->pb, out_name, AVIO_FLAG_READ_WRITE);if (ret1 < 0){cout << "打开输出文件失败, errorCode" << ret1 << endl;return -1;}// 构建一个新streamAVStream *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;// 注册ffmpegav_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[] = ".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; //初始化为0pAVCodecContext->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数据通过转换成RGB32img_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)); //分配一个packetcout<< "打印流信息" << 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){ret = avcodec_send_packet(pAVCodecContext, pAVPacket);if (ret < 0){av_packet_unref(pAVPacket);continue;}do{// 读取帧ret = avcodec_receive_frame(pAVCodecContext, pAVFrame);if (ret < 0)break;else if (ret == 0){ /* Got a frame successfully */sws_scale(img_convert_ctx, pAVFrame->data, pAVFrame->linesize, 0, pAVCodecContext->height, pAVFrameRGB->data, pAVFrameRGB->linesize);// 注释的这一段程序用来存储YUV的 Y 通道灰度图片// char frame_filename[1024];// snprintf(frame_filename, sizeof(frame_filename), "%s-%d.pgm", "frame", pCodecCtx->frame_number);// save_gray_frame(pFrame->data[0], pFrame->linesize[0], pFrame->width, pFrame->height, frame_filename);snprintf(buf, sizeof(buf), "picture-%d.jpg", frame_count);savePicture(pAVFrame, buf);frame_count++;}else if (ret == AVERROR_EOF){avcodec_flush_buffers(pAVCodecContext);break;}} while (ret != AVERROR(EAGAIN));av_packet_unref(pAVPacket);// 截取几张if (frame_count >= CAPTURE_COUNT)break;}else{av_packet_unref(pAVPacket);continue;}av_free_packet(pAVPacket); //释放资源,否则内存会一直上升}av_free(out_buffer);av_free(pAVFrameRGB);avcodec_close(pAVCodecContext);avformat_close_input(&pAVFormatContext);return 0;
}
发布评论