1小时学会:最简单的iOS直播推流(五)yuv、pcm数据的介绍和获取

1 篇文章 0 订阅

最简单的iOS 推流代码,视频捕获,软编码(faac,x264),硬编码(aac,h264),美颜,flv编码,rtmp协议,陆续更新代码解析,你想学的知识这里都有,愿意懂直播技术的同学快来看!!

源代码:https://github.com/hardman/AWLive

前面介绍了如何通过相机实时获取音视频数据。

我们接下来就需要了解获取到的数据到底是什么样的。

使用系统提供的接口获取到的音视频数据都保存在CMSampleBufferRef中。

使用GPUImamge获取到的音频数据为CMSampleBufferRef,获取到的视频格式为BGRA格式的二进制数据。

CMSampleBufferRef介绍

这个结构在iOS中表示一帧音频/视频数据。

它里面包含了这一帧数据的内容和格式。

我们可以把它的内容取出来,提取出/转换成 我们想要的数据。

代表视频的CMSampleBufferRef中保存的数据是yuv420格式的视频帧(因为我们在视频输出设置中将输出格式设为:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)。

代表音频的CMSampleBufferRef中保存的数据是PCM格式的音频帧。

yuv是什么?NV12又是什么?

视频是由一帧一帧的数据连接而成,而一帧视频数据其实就是一张图片。

yuv是一种图片储存格式,跟RGB格式类似。

RGB格式的图片很好理解,计算机中的大多数图片,都是以RGB格式存储的。

yuv中,y表示亮度,单独只有y数据就可以形成一张图片,只不过这张图片是灰色的。u和v表示色差(u和v也被称为:Cb-蓝色差,Cr-红色差),

为什么要yuv?

有一定历史原因,最早的电视信号,为了兼容黑白电视,采用的就是yuv格式。

一张yuv的图像,去掉uv,只保留y,这张图片就是黑白的。

而且yuv可以通过抛弃色差来进行带宽优化。

比如yuv420格式图像相比RGB来说,要节省一半的字节大小,抛弃相邻的色差对于人眼来说,差别不大。

一张yuv格式的图像,占用字节数为 (width * height + (width * height) / 4 + (width * height) / 4) = (width * height) * 3 / 2 
一张RGB格式的图像,占用字节数为(width * height) * 3

在传输上,yuv格式的视频也更灵活(yuv3种数据可分别传输)。

很多视频编码器最初是不支持rgb格式的。但是所有的视频编码器都支持yuv格式。

综合来讲,我们选择使用yuv格式,所以我们编码之前,首先将视频数据转成yuv格式。

我们这里使用的就是yuv420格式的视频。

yuv420也包含不同的数据排列格式:I420,NV12,NV21.

其格式分别如下, 
I420格式:y,u,v 3个部分分别存储:Y0,Y1…Yn,U0,U1…Un/2,V0,V1…Vn/2 
NV12格式:y和uv 2个部分分别存储:Y0,Y1…Yn,U0,V0,U1,V1…Un/2,Vn/2 
NV21格式:同NV12,只是U和V的顺序相反。

综合来说,除了存储顺序不同之外,上述格式对于显示来说没有任何区别。

使用哪种视频的格式,取决于初始化相机时设置的视频输出格式。 
设置为kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange时,表示输出的视频格式为NV12; 
设置为kCVPixelFormatType_420YpCbCr8Planar时,表示使用I420。

GPUImage设置相机输出数据时,使用的就是NV12.

为了一致,我们这里也选择NV12格式输出视频。

PCM是什么?

脉冲编码调制,其实是将不规则的模拟信号转换成数字信号,这样就可以通过物理介质存储起来。

而声音也是一种特定频率(20-20000HZ)的模拟信号,也可以通过这种技术转换成数字信号,从而保存下来。

PCM格式,就是录制声音时,保存的最原始的声音数据格式。

相信你应该听说过wav格式的音频,它其实就是给PCM数据流加上一段header数据,就成为了wav格式。

而wav格式有时候之所以被称为无损格式,就是因为他保存的是原始pcm数据(也跟采样率和比特率有关)。

像我们耳熟能详的那些音频格式,mp3,aac等等,都是有损压缩,为了节约占用空间,在很少损失音效的基础上,进行最大程度的压缩。

所有的音频编码器,都支持pcm编码,而且录制的声音,默认也是PCM格式,所以我们下一步就是要获取录制的PCM数据。

从CMSampleBufferRef中提取yuv数据

在前面的文章(使用系统接口捕获视频)中,初始化输出设备时,我们将输出的数据设置为kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange。 
因此在CMSampleBufferRef中保存的是yuv420(NV12)格式数据。 
通过下面的方法将CMSampleBufferRef转为yuv420(NV12)。

// AWVideoEncoder.m文件
-(NSData *) convertVideoSmapleBufferToYuvData:(CMSampleBufferRef) videoSample{
    // 获取yuv数据
    // 通过CMSampleBufferGetImageBuffer方法,获得CVImageBufferRef。
    // 这里面就包含了yuv420(NV12)数据的指针
    CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(videoSample);

    //表示开始操作数据
    CVPixelBufferLockBaseAddress(pixelBuffer, 0);

    //图像宽度(像素)
    size_t pixelWidth = CVPixelBufferGetWidth(pixelBuffer);
    //图像高度(像素)
    size_t pixelHeight = CVPixelBufferGetHeight(pixelBuffer);
    //yuv中的y所占字节数
    size_t y_size = pixelWidth * pixelHeight;
    //yuv中的uv所占的字节数
    size_t uv_size = y_size / 2;

    uint8_t *yuv_frame = aw_alloc(uv_size + y_size);

    //获取CVImageBufferRef中的y数据
    uint8_t *y_frame = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
    memcpy(yuv_frame, y_frame, y_size);

    //获取CMVImageBufferRef中的uv数据
    uint8_t *uv_frame = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);
    memcpy(yuv_frame + y_size, uv_frame, uv_size);

    CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);

    //返回数据
    return [NSData dataWithBytesNoCopy:yuv_frame length:y_size + uv_size];
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

将GPUImage获取到的BGRA格式的图片转成yuv(NV12)格式

//AWGPUImageAVCapture.m文件
-(void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex{
    [super newFrameReadyAtTime:frameTime atIndex:textureIndex];
    if(!self.capture || !self.capture.isCapturing){
        return;
    }
    //将bgra转为yuv
    //图像宽度
    int width = imageSize.width;
    //图像高度
    int height = imageSize.height;
    //宽*高
    int w_x_h = width * height;
    //yuv数据长度 = (宽 * 高) * 3 / 2
    int yuv_len = w_x_h * 3 / 2;

    //yuv数据
    uint8_t *yuv_bytes = malloc(yuv_len);

    //ARGBToNV12这个函数是libyuv这个第三方库提供的一个将bgra图片转为yuv420格式的一个函数。
    //libyuv是google提供的高性能的图片转码操作。支持大量关于图片的各种高效操作,是视频推流不可缺少的重要组件,你值得拥有。
    [self lockFramebufferForReading];
    ARGBToNV12(self.rawBytesForImage, width * 4, yuv_bytes, width, yuv_bytes + w_x_h, width, width, height);
    [self unlockFramebufferAfterReading];

    NSData *yuvData = [NSData dataWithBytesNoCopy:yuv_bytes length:yuv_len];

    [self.capture sendVideoYuvData:yuvData];
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

从CMSampleBufferRef中提取PCM数据

// AWAudioEncoder.m 文件
-(NSData *) convertAudioSmapleBufferToPcmData:(CMSampleBufferRef) audioSample{
    //获取pcm数据大小
    NSInteger audioDataSize = CMSampleBufferGetTotalSampleSize(audioSample);

    //分配空间
    int8_t *audio_data = aw_alloc((int32_t)audioDataSize);

    //获取CMBlockBufferRef
    //这个结构里面就保存了 PCM数据
    CMBlockBufferRef dataBuffer = CMSampleBufferGetDataBuffer(audioSample);
    //直接将数据copy至我们自己分配的内存中
    CMBlockBufferCopyDataBytes(dataBuffer, 0, audioDataSize, audio_data);

    //返回数据
    return [NSData dataWithBytesNoCopy:audio_data length:audioDataSize];
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

至此我们已经将捕获的视频数据转为了yuv420格式,将音频数据转为了pcm格式。

接下来就可以对这些数据进行各种编码了。编码完成后,就可以将数据发送给服务器了。

文章列表

  1. 1小时学会:最简单的iOS直播推流(一)项目介绍
  2. 1小时学会:最简单的iOS直播推流(二)代码架构概述
  3. 1小时学会:最简单的iOS直播推流(三)使用系统接口捕获音视频
  4. 1小时学会:最简单的iOS直播推流(四)如何使用GPUImage,如何美颜
  5. 1小时学会:最简单的iOS直播推流(五)yuv、pcm数据的介绍和获取
  6. 1小时学会:最简单的iOS直播推流(六)h264、aac、flv介绍
  7. 1小时学会:最简单的iOS直播推流(七)h264/aac 硬编码
  8. 软编码
  9. flv 编码与音视频时间戳同步
  10. rtmp协议
  11. sps/pps 与 AudioSpecificConfig
  12. libaw库介绍
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
YUVPCM数据编码成视频和音频文件需要使用FFmpeg库,具体的实现步骤如下: 1. 初始化FFmpeg库 在使用FFmpeg库之前,需要先进行初始化。使用av_register_all函数可以注册FFmpeg库中的所有编解码器、格式器和协议等。 ``` av_register_all(); ``` 2. 打开输出文件 使用avformat_alloc_output_context2和avio_open2函数打开输出文件,创建AVFormatContext结构体并分配内存,将输出文件与该结构体关联。 ``` AVFormatContext *out_ctx = NULL; int ret = avformat_alloc_output_context2(&out_ctx, NULL, NULL, output_file); if (ret < 0) { // 创建AVFormatContext失败 return; } if (!(out_ctx->oformat->flags & AVFMT_NOFILE)) { ret = avio_open2(&out_ctx->pb, output_file, AVIO_FLAG_WRITE, NULL, NULL); if (ret < 0) { // 打开输出文件失败 return; } } ``` 3. 创建音视频流 使用avformat_new_stream函数创建音视频流,并设置音视频流的相关参数,如编码器、帧率、码率、采样率等。 ``` AVStream *video_stream = avformat_new_stream(out_ctx, NULL); if (video_stream == NULL) { // 创建视频流失败 return; } AVCodecParameters *codecpar = video_stream->codecpar; codecpar->codec_type = AVMEDIA_TYPE_VIDEO; codecpar->width = width; codecpar->height = height; codecpar->format = AV_PIX_FMT_YUV420P; codecpar->codec_id = AV_CODEC_ID_H264; codecpar->bit_rate = bit_rate; codecpar->framerate = {fps, 1}; AVStream *audio_stream = avformat_new_stream(out_ctx, NULL); if (audio_stream == NULL) { // 创建音频流失败 return; } codecpar = audio_stream->codecpar; codecpar->codec_type = AVMEDIA_TYPE_AUDIO; codecpar->sample_rate = sample_rate; codecpar->format = AV_SAMPLE_FMT_S16; codecpar->channels = channels; codecpar->channel_layout = av_get_default_channel_layout(channels); codecpar->codec_id = AV_CODEC_ID_AAC; codecpar->bit_rate = bit_rate; ``` 4. 打开视频和音频编码器 使用avcodec_find_encoder函数查找视频和音频编码器,并使用avcodec_open2打开编码器。 ``` AVCodec *video_codec = avcodec_find_encoder(video_stream->codecpar->codec_id); if (video_codec == NULL) { // 查找视频编码器失败 return; } AVCodecContext *video_cctx = avcodec_alloc_context3(video_codec); if (video_cctx == NULL) { // 创建视频编码器上下文失败 return; } ret = avcodec_open2(video_cctx, video_codec, NULL); if (ret < 0) { // 打开视频编码器失败 return; } AVCodec *audio_codec = avcodec_find_encoder(audio_stream->codecpar->codec_id); if (audio_codec == NULL) { // 查找音频编码器失败 return; } AVCodecContext *audio_cctx = avcodec_alloc_context3(audio_codec); if (audio_cctx == NULL) { // 创建音频编码器上下文失败 return; } ret = avcodec_open2(audio_cctx, audio_codec, NULL); if (ret < 0) { // 打开音频编码器失败 return; } ``` 5. 写入视频和音频数据 使用av_frame_alloc函数创建AVFrame结构体,填充YUVPCM数据,并使用avcodec_send_frame和avcodec_receive_packet函数将数据编码成视频和音频包,最后使用av_write_frame函数将包写入输出文件。 ``` AVFrame *video_frame = av_frame_alloc(); // 填充YUV数据video_frame中 AVPacket *video_packet = av_packet_alloc(); ret = avcodec_send_frame(video_cctx, video_frame); if (ret < 0) { // 向视频编码器发送数据失败 return; } while (ret >= 0) { ret = avcodec_receive_packet(video_cctx, video_packet); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { break; } if (ret < 0) { // 从视频编码器接收数据失败 return; } av_packet_rescale_ts(video_packet, video_cctx->time_base, video_stream->time_base); video_packet->stream_index = video_stream->index; ret = av_write_frame(out_ctx, video_packet); if (ret < 0) { // 写入视频数据失败 return; } } AVFrame *audio_frame = av_frame_alloc(); // 填充PCM数据到audio_frame中 AVPacket *audio_packet = av_packet_alloc(); ret = avcodec_send_frame(audio_cctx, audio_frame); if (ret < 0) { // 向音频编码器发送数据失败 return; } while (ret >= 0) { ret = avcodec_receive_packet(audio_cctx, audio_packet); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { break; } if (ret < 0) { // 从音频编码器接收数据失败 return; } av_packet_rescale_ts(audio_packet, audio_cctx->time_base, audio_stream->time_base); audio_packet->stream_index = audio_stream->index; ret = av_write_frame(out_ctx, audio_packet); if (ret < 0) { // 写入音频数据失败 return; } } ``` 6. 关闭编码器和输出文件 使用av_write_trailer、avcodec_free_context和avformat_free_context函数释放资源并关闭编码器和输出文件。 ``` av_write_trailer(out_ctx); avcodec_free_context(&video_cctx); avcodec_free_context(&audio_cctx); avformat_free_context(out_ctx); ``` 以上是将YUVPCM数据编码成视频和音频文件的基本流程,需要注意的是各项参数的设置和数据的填充。如果需要进行更详细的配置和处理,可以参考FFmpeg库的官方文档。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值