一.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; // 用哪个AACif (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_EOFif (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上的情况,已解决。