顺便说一下h264 当中有片组的概念,其中编码片分为5种,I 片、P 片、B 片、SP 片和 SI 片 。
ES 码流是 MPEG 码流中的基本流,由视频压缩编码后的视频基 码流(Video ES)和音频压缩编码后的音频基 码流(Audio ES)组成 。
相关视频推荐:
音视频开发系列-快速掌握音视频开发基础知识(视频录制原理、视频播放原理、视频基础知识、音频基础知识)_哔哩哔哩_bilibili
音视频学习最佳实践—从FFmpeg到流媒体服务器开发_哔哩哔哩_bilibili
【免费】
FFmpeg/WebRTC/RTMP/NDK/Android音视频流媒体高级开发-学习视频教程-腾讯课堂
需要更多ffmpeg/webrtc..音视频流媒体开发学习资料加群812855908领取
文章插图
以下顺带一张 ES 码流的结构图片,作为记录学习之用
文章插图
ES 码流采用图像序列(PS)、图像组(GOP)、图像(P)、片(slice)、宏块(MB)、块(B)六层结构 。
(1)图像序列层,图像序列包括若干 GOP,序列头包 起始码和序列参数,如档次、级别、彩色图像格式、帧场选择等等;
(2)图像组层,图像组包 若干幅图像,组头包 起始码、GOP 标志等,如视频磁带记录器时间、控制码、B 帧处理码等;
(3)图像层,一幅图像包 若干片,头信息中有起始码、P 标志,如时间、参考帧号、图像类型、MV、分级等;
(4)片层,片是最小的同步单位,包 若干宏块,片头中有起始码、片地址、量化步长等;
(5)宏块层,宏块由 4 个 8×8 亮度块和 2 个色度块组成,宏块头包括宏块地址、宏块类型、运动矢量等 。7、printf(输出) 一些关于输出格式的详细数据,例如时间,比特率,数据流,容器,元数据,辅助数据,编码,时间戳等等
av_dump_format(pFormatCtx, 0, out_file, 1);
8、设置编码器// 通过 codec_id 找到对应的编码器pCodec = avcodec_find_encoder(pCodecCtx->codec_id);if (!pCodec){printf("Can not find encoder! n");return;}// 打开编码器,并设置参数 paramif (avcodec_open2(pCodecCtx, pCodec,¶m) < 0){printf("Failed to open encoder! n");return;}
9、设置原始数据 AVFrameAVFrame *pFrame = av_frame_alloc();// 通过像素格式(这里为 YUV)获取图片的真实大小,例如将 480 * 720 转换成 int 类型int picture_size = avpicture_get_size(pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);// 将 picture_size 转换成字节数据,byteunsigned char *picture_buf = (uint8_t *)av_malloc(picture_size);// 设置原始数据 AVFrame 的每一个frame 的图片大小,AVFrame 这里存储着 YUV 非压缩数据avpicture_fill((AVPicture *)pFrame, picture_buf, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);
10、准备写入数据之前,当然要先写编码的头部了// 编写 h264 封装格式的文件头部,基本上每种编码都有着自己的格式的头部,想看具体实现的同学可以看看 h264 的具体实现int ret = avformat_write_header(pFormatCtx,NULL);if (ret < 0) {printf("write header is failed");return;}
这里顺便记录一下, h264 原始码流,又称为原始码流,都是由一个一个的 NALU 组成的,结构体如下enum nal_unit_type_e{NAL_UNKNOWN = 0, // 未使用NAL_SLICE = 1, // 不分区、非 IDR 图像的片NAL_SLICE_DPA = 2, // 片分区 ANAL_SLICE_DPB = 3, // 片分区 BNAL_SLICE_DPC = 4, // 片分区 CNAL_SLICE_IDR = 5, /* ref_idc != 0 / // 序列参数集NAL_SEI = 6, / ref_idc == 0 / // 图像参数集NAL_SPS = 7, // 分界符NAL_PPS = 8, // 序列结束NAL_AUD = 9, // 码流结束NAL_FILLER = 12, // 填充/ ref_idc == 0 for 6,9,10,11,12 */};enum nal_priority_e // 优先级{NAL_PRIORITY_DISPOSABLE = 0,NAL_PRIORITY_LOW = 1,NAL_PRIORITY_HIGH = 2,NAL_PRIORITY_HIGHEST = 3,};typedef struct{int startcodeprefix_len; //! 4 for parameter sets and first slice in picture, 3 for everything else (suggested)unsigned len; //! Length of the NAL unit (Excluding the start code, which does not belong to the NALU)unsigned max_size; //! Nal Unit Buffer sizeint forbidden_bit; //! should be always FALSEint nal_reference_idc; //! NALU_PRIORITY_xxxxint nal_unit_type; //! NALU_TYPE_xxxxchar *buf; //! contains the first byte followed by the EBSP} NALU_t;
11、创建编码后的数据 AVPacket 结构体来存储 AVFrame 编码后生成的数据AVCodec* pCodec;av_new_packet(&pkt,picture_size);
>其实从这里看出 AVPacket 跟 AVFrame 的关系如下编码前:AVFrame
编码后:AVPacket
12、写入 yuv 数据到 AVFrame 结构体中
// 设置 yuv 数据中 y 图的宽高int y_size = pCodecCtx->width * pCodecCtx->height;for (int i=0; i<framenum; i++){//Read raw YUV dataif (fread(picture_buf, 1, y_size3/2, in_file) <= 0){printf("Failed to read raw data! n");return ;}else if(feof(in_file)){break;}pFrame->data[0] = picture_buf; // YpFrame->data[1] = picture_buf+ y_size; // UpFrame->data[2] = picture_buf+ y_size5/4; // V//PTS//pFrame->pts=i;// 设置这一帧的显示时间pFrame->pts=i(video_st->time_base.den)/((video_st->time_base.num)25);int got_picture=0;// 利用编码器进行编码,将 pFrame 编码后的数据传入 pkt 中int ret = avcodec_encode_video2(pCodecCtx, &pkt,pFrame, &got_picture);if(ret < 0){printf("Failed to encode! n");return ;}// 编码成功后写入 AVPacket 到 输入输出数据操作着 pFormatCtx 中,当然,记得释放内存if (got_picture==1){printf("Succeed to encode frame: %5dtsize:%5dn",framecnt,pkt.size);framecnt++;pkt.stream_index = video_st->index;ret = av_write_frame(pFormatCtx, &pkt);av_free_packet(&pkt);}}
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 前端开发之动态管理Nginx集群的方法
- 必知必会的python测试开发笔、面试题
- 一起学排序算法——选择排序
- 我的漂亮的 Linux 开发环境
- cpolar——安全的内网穿透工具
- 鸿蒙APP开发:如何实现“百度地图”的显示?需要3项认真操作才行
- Python数据分析——处理中国地区信息
- App制作的流程是什么?如何制作App开发流程?
- Java业务开发常见错误
- 程序的执行流程和开发工具介绍