#include "./rtmp_source.hpp"
#include "iostream"
using namespace std;
#define V_WIDTH 640
#define V_HEIGHT 480
static AVFormatContext *open_device()
{
int ret = 0;
char errors[1024] = {0};
// 创建输出的缓冲区
AVFormatContext *fmt_ctx = NULL;
AVDictionary *options = NULL;
char *device_name = "/dev/video0";
// 注设备信息
avdevice_register_all();
AVInputFormat *ifromat = av_find_input_format("video4linux2");
av_dict_set(&options, "video_size", "640x480", 0);
av_dict_set(&options, "framerate", "30", 0);
av_dict_set(&options, "pixel_format", "nv12", 0);
if ((ret = avformat_open_input(&fmt_ctx, device_name, ifromat, &options)) < 0)
{
av_strerror(ret, errors, 1024);
fprintf(stderr, "failede to open video device[%d]%s", ret, errors);
return NULL;
}
return fmt_ctx;
}
// 打开编码器
static void open_coder(int width, int height, AVCodecContext **enc_ctx)
{
int ret = 0;
char errors[1024];
AVCodec *codec = NULL;
// 找到对应的编码器
codec = avcodec_find_encoder_by_name("libx264");
if (!codec)
{
fprintf(stderr, "codec libx264 not foundtion!\n");
exit(-1);
}
// 申请一个AVcodec并且将其绑定到的AVCodecContext中
*enc_ctx = avcodec_alloc_context3(codec);
if (!*enc_ctx)
{
av_strerror(ret, errors, 1024);
fprintf(stderr, "Failed to open audio device, [%d]%s\n", ret, errors);
exit(-1);
}
// 设置sps pps 相关信息
(*enc_ctx)->profile = FF_PROFILE_H264_HIGH_444;
(*enc_ctx)->level = 5.0;
(*enc_ctx)->width = V_WIDTH;
(*enc_ctx)->height = V_HEIGHT;
(*enc_ctx)->gop_size = 12;
(*enc_ctx)->keyint_min = 5;
(*enc_ctx)->max_b_frames = 3;
(*enc_ctx)->has_b_frames = 1;
// 设置参考帧的数量
(*enc_ctx)->refs = 3;
// 设置输入视频的格式
(*enc_ctx)->pix_fmt = AV_PIX_FMT_YUV420P;
// 设置对用的码率
(*enc_ctx)->bit_rate = 600000;
// 设置帧率
(*enc_ctx)->time_base = AVRational{1, 25};
(*enc_ctx)->framerate = AVRational{25, 1};
// 打开对应的编码器数量
ret = avcodec_open2(*enc_ctx, codec, NULL);
if (ret < 0)
{
av_strerror(ret, errors, 1024);
fprintf(stderr, "Failed to open audio device, [%d]%s\n", ret, errors);
exit(-1);
}
}
// 打开对应的frame
static AVFrame *creat_frame(int width, int height)
{
int ret = 0;
char errors[1024];
AVFrame *frame = NULL;
if (!frame)
{
av_strerror(ret, errors, 1024);
fprintf(stderr, "Failed to open audio device, [%d]%s\n", ret, errors);
goto _errors;
}
frame->width = width;
frame->height = height;
frame->format = AV_PIX_FMT_YUV420P;
// 分配一个缓冲区
ret = av_frame_get_buffer(frame, 32);
if (ret < 0)
{
av_strerror(ret, errors, 1024);
fprintf(stderr, "Failed to open audio device, [%d]%s\n", ret, errors);
goto _errors;
}
return frame;
_errors:
if (frame)
{
av_frame_free(&frame);
}
return NULL;
}
static void encode(AVCodecContext *enc_ctx,
AVFrame *frame,
AVPacket *newpacket,
FILE *outfile)
{
int ret = 0;
char errors[1024];
if (!frame)
{
printf("send frame to encoder ,pts=%lld", (long long)frame->pts);
}
// 将原始数据给的编码器进行编码
ret = avcodec_send_frame(enc_ctx, frame);
if (ret < 0)
{
av_strerror(ret, errors, 1024);
fprintf(stderr, "Failed to open audio device, [%d]%s\n", ret, errors);
exit(-1);
}
// 从编码器获取编码号的输入
while (ret >= 0)
{
ret = avcodec_receive_packet(enc_ctx, newpacket);
if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN))
{
return;
}
else if (ret < 0)
{
printf("Error,failed to encode!\n");
exit(1);
}
fwrite(newpacket->data, 1, newpacket->size, outfile);
av_packet_unref(newpacket);
}
}
static void rev_video()
{
int ret = 0;
int base = 0;
char errors[1024];
AVPacket pkt;
AVFormatContext *fmt_ctx = NULL;
AVCodecContext *enc_ctx = NULL;
av_log_set_level(AV_LOG_ERROR);
char *in_file = "/home/zhao/learn/ffmpeg_learn/source/sample_vide.yuv";
char *out_file = "/home/zhao/learn/ffmpeg_learn/source/sample_vide.h264";
FILE *yuv_out_file = fopen(in_file, "wb+");
FILE *h264_out_file = fopen(out_file, "wb+");
// 建立上下文信息
fmt_ctx = open_device();
//打开编码器上下文
open_coder(V_WIDTH, V_HEIGHT, &enc_ctx);
// 创建frame缓冲区
AVFrame *frame = creat_frame(V_WIDTH, V_HEIGHT);
// 创建编码后的输出的packet
AVPacket *newpkt = av_packet_alloc();
if (!newpkt)
{
av_strerror(ret, errors, 1024);
fprintf(stderr, "Failed to open audio device, [%d]%s\n", ret, errors);
goto _errors;
}
while ((ret = av_read_frame(fmt_ctx, &pkt)) == 0)
{
int i = 0;
av_log(NULL, AV_LOG_INFO, "packet size is %d\n", pkt.size);
// nv12 YYYYYYYYUVVU
// YUV420 YYYYYYYYUUVV
memcpy(frame->data[0], pkt.data, 307200);
for (i = 0; i < 307200; i++)
{
frame->data[1][i] = pkt.data[307200 + 2 * i];
frame->data[2][i] = pkt.data[307200 + 2 * i + 1];
}
fwrite(frame->data[0], 1, 307200, yuv_out_file);
fwrite(frame->data[1], 1, 307200 / 4, yuv_out_file);
fwrite(frame->data[2], 1, 307200 / 4, yuv_out_file);
frame->pts = base++;
encode(enc_ctx, frame, newpkt, h264_out_file);
av_packet_unref(&pkt);
}
encode(enc_ctx, NULL, newpkt, h264_out_file);
_errors:
if (yuv_out_file)
{
fclose(yuv_out_file);
}
if (fmt_ctx)
{
avformat_close_input(&fmt_ctx);
}
av_log(NULL, AV_LOG_INFO, "finished ");
return;
}
int main(int argc, char *argv[])
{
rev_video();
return 0;
}
在h264流中,有两种NALU极其的重要,序列参数集(Sequence Paramater Set,SPS)和图像参数集(Picture ParamaterSet,PPS)
SPS中的信息至关重要,记录了编码的prfile、level、图像宽高等,如果其中的数据丢失或出现错误,那么解码过程很可能会失败。每一帧编码后数据所依赖的参数保存于PPS中。
一般情况SPS和PPS的NAL Unit通常位于整个码流的起始位置。封装文件一般进保存一次,位于文件头部,sps/sps再整个解码过程中复用,不发生变化。然而对于实时流,通常是从流中间开始解码,因此需要在每个I帧前添加SPS和PPS;如果编码器在编码过程中改变了码流参数(如分辨率),需要重新调整SPS和PPS数据。
ffmpeg中SPS、PPS数据从输入流中解析时,位于AVFormatContext->streams[video_index]->codecpar->extradata中。编码时,sps/pps数据存放于编码器上下文AVCodecContext->extradata中,但是该对象通常是空指针,还需要进行额外设置。
通常我们编码保存裸流时,仅第一个I帧前有SPS/PPS数据(见后续代码示例分析)。但是,如果需要做实时流传输,必须要在每一个I帧前添加SPS和PPS。其中SPS和PPS分别是14/5个字节
#include "./rtmp_source.hpp" #include "iostream" #include "signal.h" using namespace std; #define V_WIDTH 640 #define V_HEIGHT 480 bool bRuning = true; void sig_handlr(int sig_num) { bRuning = false; } int main(int argc, char const *argv[]) { signal(SIGINT, sig_handlr); int ret = 0; char errors[1024] = {0}; avdevice_register_all(); AVDictionary *options = NULL; AVFormatContext *fmt_ctx = NULL; char *device_name = "/dev/video0"; AVInputFormat *ifromat = av_find_input_format("video4linux2"); av_dict_set(&options, "video_size", "640x480", 0); av_dict_set(&options, "framerate", "25", 0); av_dict_set(&options, "pixel_format", "nv12", 0); if ((ret = avformat_open_input(&fmt_ctx, device_name, ifromat, &options)) < 0) { av_strerror(ret, errors, 1024); fprintf(stderr, "failede to open video device[%d]%s", ret, errors); return ret; } if ((ret = avformat_find_stream_info(fmt_ctx, NULL)) < 0) { av_log(NULL, AV_LOG_ERROR, "Cannot find stream information\n"); return ret; } av_dump_format(fmt_ctx, 0, device_name, 0); if ((av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, NULL)) < 0) { av_strerror(ret, errors, 1024); fprintf(stderr, "failede to open video device[%d]%s", ret, errors); avformat_close_input(&fmt_ctx); return ret; } int video_stream_index = ret; // 初始化解码器 AVCodecParameters *codepar = fmt_ctx->streams[video_stream_index]->codecpar; AVCodec *video_codec = avcodec_find_encoder(codepar->codec_id); if (!video_codec) { av_log(NULL, AV_LOG_ERROR, "Can't find decoer\n"); return -1; } // 视频编码器的上下文信息 AVCodecContext *video_decoder_ctx = avcodec_alloc_context3(video_codec); if (!video_decoder_ctx) { av_log(NULL, AV_LOG_ERROR, "Could not allocate a decoding context\n"); avformat_close_input(&fmt_ctx); return AVERROR(ENOMEM); } // 配置解码器上下文 if ((ret = avcodec_parameters_to_context(video_decoder_ctx, codepar)) < 0) { avformat_close_input(&fmt_ctx); avcodec_free_context(&video_decoder_ctx); return ret; } // 打开解码器 if ((ret = avcodec_open2(video_decoder_ctx, video_codec, NULL)) < 0) { avformat_close_input(&fmt_ctx); avcodec_free_context(&video_decoder_ctx); return ret; } // 编码器的数量 AVCodec *enc = avcodec_find_encoder_by_name("libx264"); AVCodecContext *enc_ctx = avcodec_alloc_context3(enc); if (!enc_ctx) { av_log(NULL, AV_LOG_ERROR, "Could not allocate a decoding context\n"); return AVERROR(ENOMEM); } enc_ctx->pix_fmt = AV_PIX_FMT_YUV420P; enc_ctx->width = 640; enc_ctx->height = 480; int fps = 25; // 降低便于查看 enc_ctx->framerate = {fps, 1}; enc_ctx->time_base = {1, fps}; enc_ctx->gop_size = fps; enc_ctx->bit_rate = 2000000; // 2M // 解码并保存到文件 uint32_t frameCnt = 0; AVPacket *pkt = av_packet_alloc(); // 分配一个AVPactet对象,用于管理其缓冲区 AVFrame *frame = av_frame_alloc(); // 分配一个AVFrame对象,用于管理其缓冲区 AVPacket *enc_pkt = av_packet_alloc(); FILE *f264 = fopen("out.h264", "wb"); while (bRuning) { ret = av_read_frame(fmt_ctx, pkt); // 循环从输入获取一帧压缩编码数据,分配pkt缓冲区 if (ret < 0) { break; } // 仅处理视频码流 if (pkt->stream_index != video_stream_index) continue; ret = avcodec_send_packet(video_decoder_ctx, pkt); // 送一帧到解码器 while (ret >= 0) { ret = avcodec_receive_frame(video_decoder_ctx, frame); // 尝试获取解码数据,分配frame缓冲区 if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { break; } else if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Error while sending a packet to the decoder\n"); goto end; } // 解码的视频数据处理 printf("\rSucceed to decode frame %d\n", frameCnt++); // 编码 ret = avcodec_send_frame(enc_ctx, frame); // 送一帧到解码器 while (ret >= 0) { ret = avcodec_receive_packet(enc_ctx, enc_pkt); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { break; } else if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Error while sending a packet to the decoder\n"); goto end; } // 判断当前数据包的前四个字节判断类型, if ((enc_pkt->data[4] & 0x1f) == 5) { fwrite(enc_ctx->extradata, 1, enc_ctx->extradata_size, f264); } printf("\rSucceed to encode frame %d\n", frameCnt); fwrite(enc_pkt->data, 1, enc_pkt->size, f264); av_packet_unref(enc_pkt); } av_frame_unref(frame); // 释放frame缓冲区数据 } av_packet_unref(pkt); // 释放pkt缓冲区数据 } end: // 关闭输入 avformat_close_input(&fmt_ctx); av_packet_free(&pkt); av_frame_free(&frame); av_dict_free(&options); fclose(f264); return 0; }