本章仅对部分代码进行讲解,以帮助读者更好的理解章节内容。
本系列文章涉及的项目HardwareVideoCodec已经开源到Github,支持软编和硬编。使用它你可以很容易的实现任何分辨率的视频编码,无需关心摄像头预览大小。一切都如此简单。目前已迭代多个稳定版本,欢迎查阅学习和使用,如有BUG或建议,欢迎Issue。


  x264是目前使用最广泛、效率最高的h264编码库,著名的音视频处理库ffmpeg也支持x264的扩展。如果你的项目用于商业用途,建议选用免费的openh264
  相比x264,可能著名的ffmpeg更广为人知。但是我们为什么不使用ffmpeg呢。正如本系列文章的序章所说,如果你只是打算用于h264编码,完全没必要使用庞大复杂ffmpeg,反而选择短小精悍的x264更适合你。不仅可以使用更小的so库(这在移动平台很有必要),而且也不需要再去啃ffmpeg枯燥复杂的代码。我是前前后后看了五遍才勉强看懂,一直处于看了又忘,忘了又看的状态,似会非会的叠加状态。相比之下x264的流程更为短小清晰,使用更为简单。

一、使用x264

  在上一章我们详细的讲解了如何编译x264,如果你尚未接触过x264,建议回头翻阅学习。

  1. 申请内存空间

  x264是一个c库,所以你需要搭建好ndk环境。要使用x264,我们首先需要为其编码器申请内存空间,这里先定义一个编码器相关的结构体。

typedef struct {x264_param_t *param;x264_t *handle;x264_picture_t *picture;x264_nal_t *nal;
} Encoder;
static Encoder *encoder = NULL;

  然后为其申请内存空间。

X264Encoder::X264Encoder() {LOGE("X264Encoder");encoder = (Encoder *) malloc(sizeof(Encoder));encoder->param = (x264_param_t *) malloc(sizeof(x264_param_t));encoder->picture = (x264_picture_t *) malloc(sizeof(x264_picture_t));
}

  2. 配置编码器

  内存申请完毕之后,还需要对编码器参数进行配置,包括分辨率bitrate帧格式fpsprofilelevel。由于我这里主要用于直播,所以使用zerolatency的配置来把延迟降到最低。需要特别注意的是,设置encoder->param->b_sliced_threads = 0encoder->param->i_threads = X264_THREADS_AUTO能大幅度提高编码效率,不知道为什么,部分资料说是开启了多帧并行编码
  另外x264还有非常非常多的可配置参数,但如果要开始使用,简单配置上面的几个参数就可以了。更多的可配置参数在文章末尾提供的源码中有注释,但不一定准确,因为我目前也没完全弄懂这些参数的作用,以及该怎么配合使用,泪目。如果有人知道的话,请你一定要告诉我,感谢。

static void config() {x264_param_default_preset(encoder->param, "veryfast", "zerolatency");//开启多帧并行编码encoder->param->b_sliced_threads = 0;encoder->param->i_threads = X264_THREADS_AUTO;/*** 是否复制sps和pps放在每个关键帧的前面*/encoder->param->b_repeat_headers = 0;/*** 恒定质量* ABR(平均码率)/CQP(恒定质量)/CRF(恒定码率)* ABR模式下调整i_bitrate* CQP下调整i_qp_constant调整QP值,太细致了人眼也分辨不出来,为了增加编码速度降低数据量还是设大些好* CRF下调整f_rf_constant和f_rf_constant_max影响编码速度和图像质量(数据量),码率和图像效果参数失效*/encoder->param->rc.i_rc_method = X264_RC_ABR;/*** 范围0~51,值越大图像越模糊,默认23*///encoder->param->rc.i_qp_constant = 51;/*** inter,取值范围1~32* 值越大数据量相应越少,占用带宽越低*/encoder->param->analyse.i_luma_deadzone[0] = 32;/*** intra,取值范围1~32* 值越大数据量相应越少,占用带宽越低*/encoder->param->analyse.i_luma_deadzone[1] = 32;/*** 快速P帧跳过检测*/encoder->param->analyse.b_fast_pskip = 1;/*** 是否允许非确定性时线程优化*/encoder->param->b_deterministic = 0;/*** 强制采用典型行为,而不是采用独立于cpu的优化算法*/encoder->param->b_cpu_independent = 0;
}
void X264Encoder::setVideoSize(int width, int height) {encoder->param->i_width = width; //set frame widthencoder->param->i_height = height; //set frame height
}void X264Encoder::setBitrate(int bitrate) {encoder->param->rc.i_bitrate = bitrate / 1000;
}void X264Encoder::setFrameFormat(int format) {encoder->param->i_csp = format; // 设置输入的视频采样的格式
}void X264Encoder::setFps(int fps) {encoder->param->i_fps_num = (uint32_t) fps;encoder->param->i_fps_den = 1;
}void X264Encoder::setProfile(char *profile) {x264_param_apply_profile(encoder->param, profile);
}void X264Encoder::setLevel(int level) {encoder->param->i_level_idc = level;// 11 12 13 20 for CIF;31 for 720P
}

  3. 打开编码器

  这里调用x264_encoder_open打开编码器,并为picture申请内存空间,并指定帧格式,用于储存待编码帧数据。

bool X264Encoder::start() {if (INVALID != state) {LOGI("Start failed. Invalid state, encoder is not invalid");return false;}state = START;if ((encoder->handle = x264_encoder_open(encoder->param)) == NULL) {reset();return false;}x264_picture_alloc(encoder->picture, encoder->param->i_csp, encoder->param->i_width,encoder->param->i_height);int y_size = encoder->param->i_width * encoder->param->i_height;uint8_t *buff = (uint8_t *) malloc(y_size * 3 / 2);encoder->picture->img.i_csp = X264_CSP_I420;encoder->picture->img.i_plane = 3;encoder->picture->img.plane[0] = buff;//Yencoder->picture->img.plane[1] = buff + y_size;//Uencoder->picture->img.plane[2] = buff + y_size * 5 / 4;//Vencoder->picture->img.i_stride[0] = encoder->param->i_width;encoder->picture->img.i_stride[1] = encoder->param->i_width / 2;encoder->picture->img.i_stride[2] = encoder->param->i_width / 2;return true;
}

  4. 开始编码

  使用x264_encoder_encode可以对数据进行编码,第一个参数是编码器句柄,第二个是编码后数据,第三个是输出数据的nal个数,第四个是输入的原始数据,第五个是编码后的帧信息。
  由于我的原始帧数据格式是RGBA,而我们打开编码器的时候设置的输入格式是I420(x264目前只支持这个,虽然可以设置别的格式),所以我们需要把RGBA转成I420
  这里需要注意的是,不要使用除libyuv以外的任何方法进行格式转换,特别是网上一些自己写的java或c的转换算法,这些算法效率极低,基本不可用,千万不要浪费时间尝试这些(过来人),当然学习一下是可以的。libyuv之所以效率高,是因为其使用了arm的neon扩展指令进行加速,直接跟硬件交互,速度不是普通的java和c能比的。
  libyuv是google开源的c库,需要自己编译,也可以使用别人编译好的,如果有必要,可以写一篇关于libyuv编译的教程。

bool X264Encoder::encode(char *src, char *dest, int *s, int *type) {if (START != state) {LOGI("Start failed. Invalid state, encoder is not start");return 0;}s[0] = 0;encoder->picture->i_type = X264_TYPE_AUTO;int nNal = -1;x264_picture_t pic_out;int size = 0, i = 0;struct timeval start, end;gettimeofday(&start, NULL);if (!fillSrc(src)) {LOGE("Convert failed");return false;}gettimeofday(&end, NULL);int time = end.tv_usec - start.tv_usec;gettimeofday(&start, NULL);if (x264_encoder_encode(encoder->handle, &(encoder->nal), &nNal, encoder->picture, &pic_out) <0) {return false;}for (i = 0; i < nNal; i++) {memcpy(dest, encoder->nal[i].p_payload, encoder->nal[i].i_payload);dest += encoder->nal[i].i_payload;size += encoder->nal[i].i_payload;}s[0] = size;type[0] = pic_out.i_type;gettimeofday(&end, NULL);LOGI("Encode type: %d, Yuv convert time: %d, Encode time: %ld", pic_out.i_type, time,(end.tv_usec - start.tv_usec));return true;
}

  到这里我们就可以编码出h264数据了。

二、使用MediaMuxer混合音视频

  当我们通过x264编码出h264数据后,我们就可以把视频数据跟音频数据进行混合写入到文件了。但是x264只提供了编码器,不像ffmpeg那样提供一条龙服务。那我编码出数据没法封装成文件有个luan用啊!难道我们还需要使用ffmpeg对编码数据进行封装吗?这样子的话还不如也使用ffmpeg进行编码得了。
  回想之前我们使用MediaCodec进行硬编的时候,可以使用MediaMuxer进行文件封装,那么这里我们能不能也使用这个对x264编码后的数据进行封装呢,答案是可以的!
  第六章讲MediaMuxer用法的时候我们说到,要使用MediaMuxer就必须先addTrack(MediaFormat)来添加音视频轨道,而这个方法需要一个特殊的MediaFormat,这个参数特殊在哪呢。

codec-specific data

  这个特殊之处在于codec-specific data。查看官方文档可以发现,MediaMuxer对h264进行封装的时候需要spspps,这两块数据分别对应MediaMuxer中的csd-1csd-2,这些数据可以通过MediaFormat.setByteBuffer(String name, ByteBuffer bytes)来设置,划重点!比如

mediaFormat.setByteBuffer("csd-0", sps);
mediaFormat.setByteBuffer("csd-1", pps);

  h264没有使用到csd-2,所以不需要设置。至此,我们可以像打开MediaCodec时构造MediaFormat那样设置对应的参数,然后在此基础上再给MediaFormat设置上对应的csd就可以使用MediaMuxer对x264编码出来的数据进行封装了。
  还有一个关键就是,spspps从哪里来呢。其实spspps是h264的标准头数据,保存了视频的分辨率和帧格式等数据,用来告诉解码器如何解码帧数据。而这个头数据也是可以从x264获取到的。
  在打开x264编码器之后,我们可以通过x264_encoder_headers来获取spspps

/*** * @param dest sps和pps,这里把他们保存在同一块内存,也可以分开保存* @param s sps和pps总长度* @param type 用于标记这是sps和pps* @return */
bool X264Encoder::encodeHeader(char *dest, int *s, int *type) {int nal, size = 0;x264_nal_t *nals;x264_encoder_headers(encoder->handle, &nals, &nal);for (int i = 0; i < nal; i++) {if (nals[i].i_type == NAL_SPS) {memcpy(dest, nals[i].p_payload, nals[i].i_payload);dest += nals[i].i_payload;size += nals[i].i_payload;} else if (nals[i].i_type == NAL_PPS) {memcpy(dest, nals[i].p_payload, nals[i].i_payload);dest += nals[i].i_payload;size += nals[i].i_payload;}}s[0] = size;type[0] = X264_TYPE_HEADER;return true;
}

  拿到spspps之后便可以构造出MediaMuxer所需要的特殊MediaFormat了,之后参考第六章正常使用MediaMuxer即可。如果没有spspps,最终出来的视频会绿屏或黑屏。

  至此,「Android音视频编码那点破事」系列的坑终于填完了,断断续续花了四个多月,说到底还是太懒了。感谢大家的支持,如果这个系列对你有帮助,欢迎star本开源项目,也可以点赞、评论和收藏。

本章知识点:

  1. x264的使用。
  2. MediaMuxer的另类用法。

本章相关源码·HardwareVideoCodec项目

  • SoftVideoEncoderImpl
  • CacheX264Encoder
  • X264Encoder
  • Java_com_lmy_codec_x264_X264Encoder.cpp
  • X264Encoder.cpp
  • MuxerImpl

X264实现H264编码以及MediaMuxer的另类用法「第八章,Android音视频编码那点破事」相关推荐

  1. 视频教程-FFmpeg音视频编码实战屏幕录像机视频课程-基于QT5和FFMpegSDK-C/C++

    FFmpeg音视频编码实战屏幕录像机视频课程-基于QT5和FFMpegSDK 夏曹俊:南京捷帝科技有限公司创始人,南京大学计算机硕士毕业,有15年c++跨平台项目研发的经验,领导开发过大量的c++虚拟 ...

  2. MediaCodec 、x264、faac 实现音视频编码并通过 rtmp 协议实现推流

    前言 咱们回顾一下前面 2 篇文章,主要讲解了如何搭建 rtmp 直播服务器,和如何开发一款具有拉流功能的 Android 播放器.那么现在有了播放端和直播服务器还缺少推流端.该篇文章我们就一起来实现 ...

  3. Android音视频【一】H264编码基础

    人间观察 岁月催人,时间过的太快了 音视频编码解码就是指通过特定的压缩/解压技术,将某个音视频格式的数据转换为另一种音视频格式数据.目前在Android中的音视频用的最多的就是H264+aac的方式进 ...

  4. Android音视频API - MediaCodec/MediaMuxer/MediaStore/MediaController等

    AudioTrack播放音频PCM.[Android] 混音器AudioMixer. MediaPlayer/MediaRecorder, AudioTrack/AudioRecorder, Medi ...

  5. Android 音视频采集与软编码总结

    请尊重原创,转载请注明出处:http://blog.csdn.net/mabeijianxi/article/details/75807435(本文已在 "任玉刚" 微信公众号发布 ...

  6. 又一篇关于各种音视频编码的

    编解码学习笔记(一):基本概念 媒体业务是网络的主要业务之间.尤其移动互联网业务的兴起,在运营商和应用开发商中,媒体业务份量极重,其中媒体的编解码服务涉及需求分析.应用开发.释放license收费等等 ...

  7. 走进音视频的世界——音视频编码

    音视频流是通过特定编码器压缩,由一系列的压缩图像/语音帧组成.当然可能存在多种语言多音轨,每个音轨之间的音频流相互独立.还可能存在内置字幕,常见的字幕格式有sub.smi.ssa.srt等.但是,本篇 ...

  8. 研究Android音视频-3-在Android设备上采集音视频并使用MediaCodec编码为H.264

    原文 : https://juejin.cn/post/69601302052266311754 本文解决的问题 本文主要使用 MediaCodec 硬编码器对 Android 设备采集的音视频编码 ...

  9. android硬编码封装mp4,【Android 音视频开发打怪升级:音视频硬解码篇】四、音视频解封和封装:生成一个MP4...

    [声 明] 首先,这一系列文章均基于自己的理解和实践,可能有不对的地方,欢迎大家指正. 其次,这是一个入门系列,涉及的知识也仅限于够用,深入的知识网上也有许许多多的博文供大家学习了. 最后,写文章过程 ...

  10. Android音视频视频基础(H264)二 SPS分析

    学习目标: SPS分析与读取 图中的标识为哥伦布编码的,均需要通过哥伦布编码解析实际值. 学习内容: 首先肯定得截个sps数据的图来看分析了. 上图红框内就是sps数据了.67是sps标识,不清楚的小 ...

最新文章

  1. 周围剃光头顶留长发型_为什么很多秃头的人,宁愿周围留一圈头发,也不愿剃成光头?...
  2. ie6多文件上传_一个好的“文件上传”功能必须要注意的这些点你都知道吗?
  3. 解决pip安装时速度慢的问题 镜像源(pip install -i [镜像源地址] [包名])
  4. 【已解决】CMake Error: Cannot determine link language for target “xxx“. CMake Error: CMake can not determ
  5. 使用Area(区域)会遇到的问题
  6. 2pin接口耳机_拆解报告:雷柏首款真无线耳机XS200
  7. vc 通过句柄修改窗口大小_漫画:对象是如何被找到的?句柄 OR 直接指针?
  8. activiti 生命周期_一文让你读懂什么是Activiti工作流
  9. 机器学习实践笔记(二)EOF
  10. PAT (Basic Level) Practice1012 数字分类
  11. 帮助文档.chm能打开但是显示不出来详细内容
  12. python中abs和fabs的区别_Python - abs vs fabs
  13. re 正则表达式匹配中文
  14. 我的python程序_我试着运行我的python程序,但当我运行它时什么也没有发生
  15. 计算机软件研究方法与技术路线,开题报告研究方法与技术路线.doc
  16. 远程云服务器闪退_远程服务器连接 闪退
  17. 从《龙之战》想起 前一段时间,和江浙地区的企业信息化CIO们聊天,也深刻
  18. mysql中my.cnf不生效解决
  19. jasper支持哪些html标签,Jasper HTML输出宽度问题(示例代码)
  20. Elasticsearch 6.4 ingest-attachment对文件IK分词器全文检索

热门文章

  1. 云计算的三种服务模式:IaaS,PaaS和SaaS
  2. 数据库系统概论-数据库恢复技术
  3. 无线路由登不上服务器怎么办,192.168 2.1路由器登陆不了怎么办
  4. 想长胖的人看过来,几招教你变胖|猎人营
  5. SPSS应用多元逻辑回归解决无序多分类问题
  6. 开放微博社区使用OAUTH协议简介
  7. ODM、JDM、OEM、OBM
  8. 多维Ellipse(椭球)形状与方程对应关系分析
  9. 解决nginx error!The page is temporarily unavailable.
  10. IELTS12 Test6 the population of some countries include a relatively large number of young adults