作者简介
jzg,携程资深前端开发工程师,专注Android开发;
zx,携程高级前端开发工程师,专注iOS开发;
zcc,携程资深前端开发工程师,专注iOS开发。
RGB:RGB的颜色模式应该是我们最熟悉的一种,在现在的电子设备中应用广泛。通过R、G、B三种基础色,可以混合出所有的颜色。
YUV:YUV是一种亮度与色度分离的色彩格式,三个字母的意义分别为:
Y:亮度,就是灰度值。除了表示亮度信号外,还含有较多的绿色通道量。单纯的Y分量可以显示出完整的黑白图像。
U:蓝色通道与亮度的差值。
V:红色通道与亮度的差值。
其中,U、V分量分别表示蓝(blue)、红(red)分量信号,只含有色度信息,所以YUV也称为YCbCr,其中,Cb、Cr的含义等同于U、V,C可以理解为component或者color。
RGB和YUV的换算
Y = 0.299R + 0.587G + 0.114BU = -0.147R - 0.289G + 0.436BV = 0.615R - 0.515G - 0.100B R = Y + 1.14VG = Y - 0.39U - 0.58VB = Y + 2.03U
1.2 音频的基础知识
一般常见的视频编码格式有H26x系列和MPEG系列。
H26x(1/2/3/4/5)系列由ITU(International Telecommunication Union)国际电传视讯联盟主导。
MPEG(1/2/3/4)系列由MPEG(Moving Picture Experts Group, ISO旗下的组织)主导。
H264是新一代的编码标准,以高压缩高质量和支持多种网络的流媒体传输著称。iOS 8.0及以上苹果开放了VideoToolbox框架来实现H264硬编码,开发者可以利用VideoToolbox框架很方便地实现视频的硬编码。
低码率
高质量的图像
容错能力强
网络适应性强
AAC是目前比较热门的有损压缩编码技术,并且衍生了LC-AAC,HE-AAC,HE-AAC v2 三种主要编码格式 特点:在小于128Kbit/s的码率下表现优异,并且多用于视频中的音频编码 使用场合:128Kbit/s以下的音频编码,多用于视频中音频轨的编码
WAV
在PCM数据格式的前面加上44字节,描述PCM的采样率、声道数、数据格式等信息,不会压缩 特点:音质好,大量软件支持
使用场合:多媒体开发的中间文件、保存音乐和音效素材
使用LAME编码
特点:音质在128kbit/s以上表现不错,压缩比较高,大量软件硬件都支持,兼容性好
使用场合:高比特率(传输效率 bps, 这里的b是位,不是比特)对兼容性有要求的音乐欣赏
特点:可以用比MP3更小的码率实现比MP3更好的音质,高中低码率下均有良好的表现
不足:兼容性不够好,流媒体特性不支持
适合场景:语音聊天的音频消息场景
无损压缩
专门针对PCM音频的特点设计的压缩方式,而且可以使用播放器直接播放FLAC压缩的文件
免费,支持大多数操作系统
我们可以看到左边是input,右边是output。这里要分两种情况来讨论:
1)利用MediaCodec进行解码的时候,输入input是待解码的buffer数据,输出output是解码好的buffer数据。
2)利用MediaCodec进行编码的时候,输入input是一个待编码的数据,输出output是编码好的buffer数据。
val width = 720
val height = 1280
val bitrate = 5000
val encodeType = "video/avc"
//配置用于编码的MediaCodec
val mCodec = MediaCodec.createEncoderByType(encodeType)
val outputFormat = MediaFormat.createVideoFormat(encodeType, width, height)
outputFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate)
outputFormat.setInteger(MediaFormat.KEY_FRAME_RATE, DEFAULT_ENCODE_FRAME_RATE)
outputFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1)
outputFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
outputFormat.setInteger(
MediaFormat.KEY_BITRATE_MODE,
MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CQ
)
}
codec.configure(outputFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
//这一步很关键,这一步得到的surface后面将会用到
val mSurface = codec.createInputSurface()
mCodec.start()
val mOutputBuffers = mCodec.outputBuffers
val mInputBuffers = mCodec.inputBuffers
以上是MediaCodec的作为编码器的基本配置,其中MediaCodec.createInputSurface()这个方法可以为我们创建一个用于向MediaCodec进行输入的surface。这样通过MediaCodec就能获取到编码后的数据了。用这样的方式编码我们不需要向MedaiCodec输入待编码的数据,MediaCodec会自动将输入到surface的数据进行编码。
//1,创建 EGLDisplay
val mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY)
// 2,初始化 EGLDisplay
val version = IntArray(2)
EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)
// 3,初始化EGLConfig,EGLContext上下文
val config :EGLConfig? = null
if (mEGLContext === EGL14.EGL_NO_CONTEXT) {
var renderableType = EGL14.EGL_OPENGL_ES2_BIT
val attrList = intArrayOf(
EGL14.EGL_RED_SIZE, 8,
EGL14.EGL_GREEN_SIZE, 8,
EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_ALPHA_SIZE, 8,
EGL14.EGL_RENDERABLE_TYPE, renderableType,
EGL14.EGL_NONE, 0,
EGL14.EGL_NONE
)
//配置Android指定的标记
if (flags and FLAG_RECORDABLE != 0) {
attrList[attrList.size - 3] = EGL_RECORDABLE_ANDROID
attrList[attrList.size - 2] = 1
}
val configs = arrayOfNulls<EGLConfig>(1)
val numConfigs = IntArray(1)
//获取可用的EGL配置列表
if (!EGL14.eglChooseConfig(mEGLDisplay, attrList, 0,
configs, 0, configs.size,
numConfigs, 0)) {
configs[0]
}
val attr2List = intArrayOf(EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE)
val context = EGL14.eglCreateContext(
mEGLDisplay, config, sharedContext,
attr2List, 0
)
mEGLConfig = config
mEGLContext = context
}
//这里还需要创建一个EGL用于输出的surface,这里的参数就可以传入上一小节介绍的利用MeddiaCodec创建的Surface
fun createWindowSurface(surface: Any): EGLSurface {
val surfaceAttr = intArrayOf(EGL14.EGL_NONE)
val eglSurface = EGL14.eglCreateWindowSurface(
mEGLDisplay, mEGLConfig, surface,
surfaceAttr, 0)
if (eglSurface == null) {
throw RuntimeException("Surface was null")
}
return eglSurface
}
配置EGL环境后,还要一个surface作为输出,这里就是要利用MediaCodec创建的surface作为输出,即EGL的输出作为MediaCodec的输入。
//创建一个MediaMuxer,需要指定输出保存的路径,和输出保存的格式。
val mediaMuxer = MediaMuxer(path, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
//根据MediaFormat添加媒体轨道
mediaMuxer.addTrack(MediaFormat(...))
//将输入的数据,根据指定的轨道保存到指定的文件路径中。
muxer.writeSampleData(currentTrackIndex, inputBuffer, bufferInfo);
//结合上面的所说的使用MediaCodec获取到已编码的数据
//当前帧的信息
var mBufferInfo = MediaCodec.BufferInfo()
//编码输出缓冲区
var mOutputBuffers: Array<ByteBuffer>? = null
//获取到编码输出的索引,写到指定的保存路径
val index = mCodec.dequeueOutputBuffer(mBufferInfo, 1000)
muxer.writeSampleData(currentTrackIndex,mOutputBuffers[index],mBufferInfo)
2.1.4 MediaExtractor
//根据指定文件路径创建MediaExtractor
val mediaExtractor = MediaExtractor(...)
//为MediaExtractor选择好对应的媒体轨道
mediaExtractor.selectTrack(...)
//读取一帧的数据
val inputBuffer = ByteBuffer.allocate(...)
mediaExtractor.readSampleData(inputBuffer, 0)
//进入下一帧
mediaExtractor.advance()
//MediaExtractor读取到的音频数据可以使用MediaMuxer的writeSampleData方法写入到指定的文件中
以上就是利用Android平台的硬编码相关API,将OpenGL渲染到画面编码成视频的基本流程介绍。
CMSampleBufferRef sampleBuffer = [assetReaderAudioOutput copyNextSampleBuffer];
每个AVAssetWriterInput期望以CMSampleBufferRef对象形式接收数据,如果在处理视频样本的数据时,便要将CVPixelBufferRef类型对象(像素缓冲样本数据)添加到asset writer input,这个时候就需要使用AVAssetWriterInputPixelBufferAdaptor 这个专门的适配器类。这个类在附加被包装为CVPixelBufferRef对象的视频样本时提供最佳性能。
CVPixelBufferRef pixelBuffer = NULL;
CVPixelBufferPoolCreatePixelBuffer(NULL, self.inputPixelBufferAdptor.pixelBufferPool,&pixelBuffer);
每个AVAssetWriterInputPixelBufferAdaptor都包含一个assetWriterInput,用于接收缓冲区中的数据,并且AVAssetWriterInput有一个很重要的属性readyForMoreMediaData,来标识现在缓冲区中的数据是否已经处理完成。通过判断这个属性,我们可以向AVAssetWriterInputPixelBufferAdaptor中添加数据(appendPixelBuffer:)以进行处理。
if(self.inputPixelBufferAdptor.assetWriterInput.isReadyForMoreMediaData) {
BOOL success = [self.inputPixelBufferAdptor appendPixelBuffer:newPixelBuffer withPresentationTime:self.currentSampleTime];
if (success) {
NSLog(@"append buffer success");
}
}
3.1.3 设置输入输出参数,以及多媒体数据的采样
AVAssetWriter *assetWriter = [[AVAssetWriter alloc] initWithURL:[NSURL fileURLWithPath:outFilePath] fileType:AVFileTypeMPEG4 error:&outError];
第二步:设置输出视频相关信息,如大小,编码格式H264,以及创建视频的输入类videoWriterInput,以便后续给assetReader添加videoWriterInput。
一般常见的视频编码格式有H26x系列和MPEG系列。
H26x(1/2/3/4/5)系列由ITU(International Telecommunication Union)国际电传视讯联盟主导。
MPEG(1/2/3/4)系列由MPEG(Moving Picture Experts Group, ISO旗下的组织)主导。
0
第三步:创建一个处理视频样本时专用的适配器对象,这个类在附加被包装为CVPixelBufferRef对象的视频样本时能提供最优性能。如果想要将CVPixelBufferRef类型对象添加到asset writer input,就需要使用AVAssetWriterInputPixelBufferAdaptor类。
一般常见的视频编码格式有H26x系列和MPEG系列。
H26x(1/2/3/4/5)系列由ITU(International Telecommunication Union)国际电传视讯联盟主导。
MPEG(1/2/3/4)系列由MPEG(Moving Picture Experts Group, ISO旗下的组织)主导。
1
第四步:音频数据的采集、添加音频输入
一般常见的视频编码格式有H26x系列和MPEG系列。
H26x(1/2/3/4/5)系列由ITU(International Telecommunication Union)国际电传视讯联盟主导。
MPEG(1/2/3/4)系列由MPEG(Moving Picture Experts Group, ISO旗下的组织)主导。
2
3.2 转场切换效果中的图片处理
一般常见的视频编码格式有H26x系列和MPEG系列。
H26x(1/2/3/4/5)系列由ITU(International Telecommunication Union)国际电传视讯联盟主导。
MPEG(1/2/3/4)系列由MPEG(Moving Picture Experts Group, ISO旗下的组织)主导。
3
一般常见的视频编码格式有H26x系列和MPEG系列。
H26x(1/2/3/4/5)系列由ITU(International Telecommunication Union)国际电传视讯联盟主导。
MPEG(1/2/3/4)系列由MPEG(Moving Picture Experts Group, ISO旗下的组织)主导。
4
3.2.1 如何自定义滤镜
一般常见的视频编码格式有H26x系列和MPEG系列。
H26x(1/2/3/4/5)系列由ITU(International Telecommunication Union)国际电传视讯联盟主导。
MPEG(1/2/3/4)系列由MPEG(Moving Picture Experts Group, ISO旗下的组织)主导。
5
一般常见的视频编码格式有H26x系列和MPEG系列。
H26x(1/2/3/4/5)系列由ITU(International Telecommunication Union)国际电传视讯联盟主导。
MPEG(1/2/3/4)系列由MPEG(Moving Picture Experts Group, ISO旗下的组织)主导。
6
3.2.2 了解GPUImageFilter中重点API
一般常见的视频编码格式有H26x系列和MPEG系列。
H26x(1/2/3/4/5)系列由ITU(International Telecommunication Union)国际电传视讯联盟主导。
MPEG(1/2/3/4)系列由MPEG(Moving Picture Experts Group, ISO旗下的组织)主导。
7
一般常见的视频编码格式有H26x系列和MPEG系列。
H26x(1/2/3/4/5)系列由ITU(International Telecommunication Union)国际电传视讯联盟主导。
MPEG(1/2/3/4)系列由MPEG(Moving Picture Experts Group, ISO旗下的组织)主导。
8
3)绘制完成通知所有的target处理下一帧的纹理数据
一般常见的视频编码格式有H26x系列和MPEG系列。
H26x(1/2/3/4/5)系列由ITU(International Telecommunication Union)国际电传视讯联盟主导。
MPEG(1/2/3/4)系列由MPEG(Moving Picture Experts Group, ISO旗下的组织)主导。
9
通过如上代理回调就可以得到这个过程中返回的一系列处理好的纹理样本数据。
val width = 720
val height = 1280
val bitrate = 5000
val encodeType = "video/avc"
//配置用于编码的MediaCodec
val mCodec = MediaCodec.createEncoderByType(encodeType)
val outputFormat = MediaFormat.createVideoFormat(encodeType, width, height)
outputFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate)
outputFormat.setInteger(MediaFormat.KEY_FRAME_RATE, DEFAULT_ENCODE_FRAME_RATE)
outputFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1)
outputFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
outputFormat.setInteger(
MediaFormat.KEY_BITRATE_MODE,
MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CQ
)
}
codec.configure(outputFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
//这一步很关键,这一步得到的surface后面将会用到
val mSurface = codec.createInputSurface()
mCodec.start()
val mOutputBuffers = mCodec.outputBuffers
val mInputBuffers = mCodec.inputBuffers
0
然后在这个方法中调用renderToTextureWithVertices去绘制所需的纹理,并获取到最终的帧缓存对象。以下是部分核心代码:
val width = 720
val height = 1280
val bitrate = 5000
val encodeType = "video/avc"
//配置用于编码的MediaCodec
val mCodec = MediaCodec.createEncoderByType(encodeType)
val outputFormat = MediaFormat.createVideoFormat(encodeType, width, height)
outputFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate)
outputFormat.setInteger(MediaFormat.KEY_FRAME_RATE, DEFAULT_ENCODE_FRAME_RATE)
outputFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1)
outputFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
outputFormat.setInteger(
MediaFormat.KEY_BITRATE_MODE,
MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CQ
)
}
codec.configure(outputFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
//这一步很关键,这一步得到的surface后面将会用到
val mSurface = codec.createInputSurface()
mCodec.start()
val mOutputBuffers = mCodec.outputBuffers
val mInputBuffers = mCodec.inputBuffers
1
3.2.3 pixel_buffer的写入
val width = 720
val height = 1280
val bitrate = 5000
val encodeType = "video/avc"
//配置用于编码的MediaCodec
val mCodec = MediaCodec.createEncoderByType(encodeType)
val outputFormat = MediaFormat.createVideoFormat(encodeType, width, height)
outputFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate)
outputFormat.setInteger(MediaFormat.KEY_FRAME_RATE, DEFAULT_ENCODE_FRAME_RATE)
outputFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1)
outputFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
outputFormat.setInteger(
MediaFormat.KEY_BITRATE_MODE,
MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CQ
)
}
codec.configure(outputFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
//这一步很关键,这一步得到的surface后面将会用到
val mSurface = codec.createInputSurface()
mCodec.start()
val mOutputBuffers = mCodec.outputBuffers
val mInputBuffers = mCodec.inputBuffers
2
以上便是在iOS端处理音视频合成的具体步骤,难点在于如何使用GPUImage去实现复杂的转场效果并将其写到到容器中。
“携程技术”公众号
分享,交流,成长
推荐站内搜索:最好用的开发软件、免费开源系统、渗透测试工具云盘下载、最新渗透测试资料、最新黑客工具下载……
还没有评论,来说两句吧...