一.windows
1.什么是h264?
2.h264编码流程
3.主要整体代码
int open_coder(AVCodecContext **codec_ctx)
{
// 编码器
const AVCodec *codex = avcodec_find_encoder_by_name("libfdk_aac");
// codex->capabilities = AV_CODEC_CAP_VARIABLE_FRAME_SIZE;
if (!codex)
{
fprintf(stderr, "Codec not found\n");
return -1;
}
// 编码器上下文
*codec_ctx = avcodec_alloc_context3(codex);
(*codec_ctx)->sample_fmt = AV_SAMPLE_FMT_S16; // 采样大小
(*codec_ctx)->channel_layout = AV_CH_LAYOUT_STEREO; //
(*codec_ctx)->channels = 2; // 声道数
(*codec_ctx)->sample_rate = 44100; // 采样率
(*codec_ctx)->bit_rate = 0; // AAC 128k;AAC HE 64k; AAC_HE V2:32K
// codec_ctx->profile = FF_PROFILE_AAC_HE_V2; // 用哪个AAC
if (avcodec_open2(*codec_ctx, codex, NULL) < 0)
{
fprintf(stderr, "failed avcodec_open2 \n");
return -1;
}
return 0;
}
int encode(AVCodecContext *codec_ctx, AVFrame *avframe, AVPacket *outpkt, FILE *outfile)
{
// printf("coder:%d\n", codec_ctx->codec->pix_fmts);
// printf("sned frame to encoder ,pts=%lld\n", avframe->pts);
int ret = avcodec_send_frame(codec_ctx, avframe);
if (ret < 0)
{
fprintf(stderr, "Error sending frame to encoder\n");
return -1;
}
while (ret >= 0)
{
// 获取编码后的音频数据
// printf("pre-avcodec_receive_packet\n");
ret = avcodec_receive_packet(codec_ctx, outpkt);
// printf("avcodec_receive_packet:%d\n", ret);
// 如果编码器数据不足时会返回 EAGAIN,或者到数据尾时会返回 AVERROR_EOF
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
return 0;
}
else if (ret < 0)
{
printf("Error, Failed to encode!\n");
exit(1);
}
printf("fwrite - outpkt.size: %d \n", outpkt->size);
fwrite(outpkt->data, 1, outpkt->size, outfile);
fflush(outfile);
av_packet_unref(outpkt);
}
return 0;
}
int read_video()
{
int ret = 0;
char errors[1024];
AVFormatContext *fmt_ctx = NULL;
AVDictionary *options = NULL;
AVAudioFifo *fifo = nullptr;
// FILE *outfile = fopen("./out.yuv", "wb+");
FILE *outfile_yuv = fopen("./out1.yuv", "wb+");
FILE *outfile_h264 = fopen("./out2.h264", "wb+");
if (outfile_yuv == nullptr)
{
printf("filed open out file\n");
}
AVPacket pkt;
av_init_packet(&pkt);
AVPacket *outpkt = av_packet_alloc();
av_init_packet(outpkt);
int frame_count = 0;
// 找到采集工具
const AVInputFormat *iformat = av_find_input_format("dshow");
if (iformat == NULL)
{
printf("AVInputFormat find failed \n");
return -1;
}
// 打开视频设备
av_dict_set(&options, "video_size", "1280x720", 0);
av_dict_set(&options, "framerate", "10", 0);
av_dict_set(&options, "pixel_format", "yuyv422", 0);
av_dict_set(&options, "rtbufsize", "20M", 0);
ret = open_dev(&fmt_ctx, "video=Integrated Camera", iformat, &options);
AVCodecContext *codex_ctx;
ret = open_encoder(1280, 720, &codex_ctx);
if (ret != 0)
{
printf("failed open codecx\n");
return -1;
}
// 初始化格式转换上下文
struct SwsContext *sws_ctx = sws_getContext(
1280, 720, AV_PIX_FMT_YUYV422, // 输入图像的宽度、高度和像素格式
1280, 720, AV_PIX_FMT_YUV420P, // 输出图像的宽度、高度和像素格式
SWS_BILINEAR, // 转换算法
NULL, NULL, NULL // 额外参数
);
AVFrame *frame = av_frame_alloc();
AVFrame *outFrame = av_frame_alloc();
// 设置 YUV 422P 帧
frame->format = AV_PIX_FMT_YUYV422;
frame->width = 1280;
frame->height = 720;
// 设置 YUV 420P 帧
outFrame->format = AV_PIX_FMT_YUV420P;
outFrame->width = 1280;
outFrame->height = 720;
// 分配数据
ret = av_image_alloc(frame->data, frame->linesize, 1280, 720, AV_PIX_FMT_YUYV422, 1);
if (ret < 0)
{
fprintf(stderr, "无法分配图像数据\n");
return -1;
}
ret = av_image_alloc(outFrame->data, outFrame->linesize, 1280, 720, AV_PIX_FMT_YUV420P, 1);
if (ret < 0)
{
fprintf(stderr, "无法分配图像数据\n");
return -1;
}
int count_ = 0;
// 读取数据包并解码
size_t written_y = 0, written_u = 0, written_v = 0;
while (av_read_frame(fmt_ctx, &pkt) >= 0 && count_++ < 100)
{
// fwrite(pkt.data, 1, pkt.size, outfile_yuv);
// fflush(outfile_yuv);
memcpy(frame->data[0], pkt.data, 1280 * 720 * 2);
fwrite(pkt.data, 1, pkt.size, outfile_yuv);
fflush(outfile_yuv);
// frame->pts = count_;
// printf("pkt-size:%d , outframe->format:%d\n", pkt.size, frame->format);
sws_scale_frame(sws_ctx, outFrame, frame);
outFrame->pts = count_;
// printf("pkt-size:%d , outframe->format:%d\n", pkt.size, outFrame->format);
// // // yuv420写入文件
// write_yuv_data(outFrame, outfile_yuv);
// av_packet_unref(&pkt);
encode(codex_ctx, outFrame, outpkt, outfile_h264);
av_packet_unref(&pkt);
}
encode(codex_ctx, NULL, outpkt, outfile_h264);
avformat_close_input(&fmt_ctx);
fclose(outfile_yuv);
fclose(outfile_h264);
av_log(NULL, AV_LOG_DEBUG, "end");
return 0;
}
4.遇到问题
(1)摄像头录制的原始数据为yuyv422,可以在编码器中直接设置输入格式为yuyv422,也可以先把yuyv422转换为yuv420(为什么要转?练练手)
注意:(1)yuyv422的数据格式:两个y分量和一个u、一个v分量共占4个字节,也就是每个分量占一个字节,但是两个y是共用u和v分量的,所以两个y就有了两个像素,算起来我们就可以说4个字节里面有2个像素,所以可以得到yuyv422的每个像素占2个字节,所以每个yuyv422帧的字节数为:w*h*2
(2)yuv420p的数据格式:四个y分量和一个u、一个v分量共占6个字节,同上,6/4=1.5,所以每个yuv420p像素占的字节数为1.5,得到每个yuv420p帧的字节数为:w*h*1.5
(2)编码出来的视频数据少了几秒???
YUV数据是正常的,但是编码成h264出来数据就少了,为什么呢?我检查了编码器的配置,发现并没有问题,那就是编码的过程中,最后是第二天早上仔细看了一下,原来我编码的代码里面第一行打印了frame的参数,但是最后冲刷编码器的时候,传入的是NULL,导致最后冲刷解码器崩溃了,不过并没有报错,只是停止了编码并卡顿了一下,这是我在windows上的情况,已解决。