其实对音频确实也没过多的研究,主要还是研究视频方面,不过有视频也有要音频,这样才是鲜活的视频,如果没音频看着视频也是假的,所以还是有必要学习一下音频。

13.1 音频编码

音频编码比较简单,直接使用FFmpeg的音频编码Demo就可以了,我这里也简单的列出调用的函数即可。
初始化函数:

1._code = avcodec_find_encoder(AV_CODEC_ID_AAC);   //老规矩,找到编码器
2._ctx = avcodec_alloc_context3(_codec);          //通过编码器找到context
3.avcodec_open2(_ctx, _codec, NULL);               //初始化编码器
4._frame = av_frame_alloc();
5.av_frame_get_buffer(_frame, 0);                   //申请一个帧,并获取帧的buff

编码函数也比较简单:

1.av_init_packet(&pkt);                                          //初始化一个packet
2.avcodec_encode_audio2(_ctx, &pkt, frame, &got_output);        //进行音频编码

13.2 音频重采样

重采样初始化,这个也是有demo程序的,所以只是把函数的主干列出来就可以了:

1._src_channels = av_get_channel_layout_nb_channels(_resample_params.src_channel_layout) //通过布局情况,获取通道数
2._dst_channels = av_get_channel_layout_nb_channels(_resample_params.dst_channel_layout);
3._audio_fifo = av_audio_fifo_alloc(_resample_params.dst_sample_fmt, _dst_channels, 1);    //以目标帧率为主,申请一个buff
4.swr_alloc();                      //申请一个视频重采样的结构
5.swr_init(_swr_ctx);               //填充数据,并且初始化swr
6.av_samples_alloc_array_and_samples(&_resampled_data, &linesize, _dst_channels, _resampled_data_size, _resample_params.dst_sample_fmt, 0);     //申请接收数据

初始化完成了之后,就可以直接使用了,使用的步骤:

1.auto ret = _audio_resampler->SendResampleFrame(pcm, size);   //采样到一帧数据之后,就会调用发送重采样函数
1.1 av_rescale_rnd      //计算样本的目的数量
1.2 swr_convert         //音频转化
1.3 av_audio_fifo_write     //把转化后的数据写回audio_fifo2._audio_resampler->ReceiveResampledFrame(resampled_frames,_audio_encoder->GetFrameSampleSize());
//接收音频重采样后的数据
2.1  av_audio_fifo_read(_audio_fifo, (void **)frame->data, desired_size);//通过读取一帧的数据,然后返回然后把重采样到的数据送去音频编码

感觉写的很烂,但是不简单了解而无法进入下一步的学习,然后烂就烂把,反正以后再做修改就好了。

13.3 音频采集

音频采集我觉得就不用写了,这次音频采集跟视频采集是一样的,都是直接读取pcm文件,因为我们现在重点应该在rtmp上,所以这些采集可以先通过文件获取,反正以后需要其他方式采集的话,就重写一个类,替换现在的采集类就可以了。

13.4 rtmp推音频流

上面已经采集并编码好音频数据了,现在就可以做推流处理了,

case RTMP_BODY_AUD_SPEC:
{AudioSpecMsg* audio_spec = (AudioSpecMsg*)data;uint8_t aac_spec_[4];aac_spec_[0] = 0xAF;   //aac_spec_[1] = 0x0;     // 0 = aac sequence headeraacRtmpPackager::GetAudioSpecificConfig(&aac_spec_[2], audio_spec->_profile,audio_spec->_sample_rate, audio_spec->_channels);SendAudioSpecificConfig((char *)aac_spec_, 4);break;}

第一个字节0xAF,要回到FLV格式解析音频那一节看就明白了,那个位数对应什么我这里就不解析了,我只贴了一个flv格式的文件出来,看到音频的第一个字节就是0xAF

第二个字节就简单了:
如果是AAC配置信息,就是0,如果是裸数据就是1,

flv格式的数据准备好了之后,我们来看一下aac的编码格式,不过这个函数并没有按照AAC的编码格式来写的,详细分析AAC可以会到上一节,

int AACRTMPPackager::GetAudioSpecificConfig(uint8_t* data, const uint32_t profile,const uint32_t samplerate,const uint32_t channel_num)
{//uint8_t type:5;//编码结构类型,AAC main编码为1,LOW低复杂度编码为2,SSR为3//uint8_t sample_rate:4;//采样率//uint8_t channel_num:4;//声道数//uint8_t tail:3;//最后3位固定为0uint16_t _profile = (uint16_t)profile+1;       //哪个级别的AAC_profile <<= 11;uint32_t _samplerate = 0;             //那个频率的采样switch (samplerate){case  96000:_samplerate = 0;break;case 88200:_samplerate = 1;break;case 64000:_samplerate = 2;break;case 48000:_samplerate = 3;break;case 44100:_samplerate = 4;break;case 32000:_samplerate = 5;break;case 24000:_samplerate = 6;break;case 22050:_samplerate = 7;break;case 16000:_samplerate = 8;break;case 12000:_samplerate = 9;break;case 11025:_samplerate = 10;break;case 8000:_samplerate = 11;break;case 7350:_samplerate = 12;break;default:_samplerate = 4;return -1;break;}_samplerate <<= 7;uint16_t _channel_num = (uint16_t)channel_num;     //通道_channel_num <<= 3;// 2        4  1            3uint16_t audio_spec = _profile | _samplerate | _channel_num;//这个就是自己数据的封装,到解码的时候,只有自己的解码程序才能解data[0] = (uint8_t)(audio_spec >> 8);data[1] = 0xff & audio_spec;return 0;
}

发送裸数据就比较简单了,只要填充两个字节的数据就可以了,然后调用rtmp发送函数直接发送

aud_raw_msg->data[0] = 0xaf;
aud_raw_msg->data[1] = 0x01;    // 1 =  raw data数据
memcpy(&aud_raw_msg->data[2], _aac_buf, aac_size);
_rtmp_pusher->post(RTMP_BODY_AUD_RAW, aud_raw_msg);

13.5 rtmp接收音频流

我们来接收一下发送端发送过来的音频流,先接收音频的配置信息:

// AAC sequenceif (sequence){format = (packet.m_body[0] & 0xf0) >> 4;               //这个就是分析0xAF的  //音频格式samplerate = (packet.m_body[0] & 0x0c) >> 2;          //采样率sampledepth = (packet.m_body[0] & 0x02) >> 1;           //采样的长度 type = packet.m_body[0] & 0x01;                        //音频类型// sequence = packet.m_body[1];// AAC(AudioSpecificConfig)if (format == 10) {     // AAC格式uint8_t ch0 = packet.m_body[2];         //这个字节的数据,就是我们自己封装的数据了uint8_t ch1 = packet.m_body[3];uint16_t config = ((ch0 << 8) | ch1);_profile = (config & 0xF800) >> 11;     //这个就是我们自己发过来的数据了,但是我们没有按adts格式发送_sample_frequency_index = (config & 0x0780) >> 7;_channels = (config & 0x78) >> 3;frame_length_flag = (config & 0x04) >> 2;depend_on_core_coder = (config & 0x02) >> 1;extension_flag = config & 0x01;}// Speex(Fix data here, so no need to parse...)else if (format == 11) {    // MP3格式// 16 KHz, mono, 16bit/sampletype = 0;sampledepth = 1;samplerate = 4;}audio_sample_rate = rtmpbase::GetSampleRateByFreqIdx(_sample_frequency_index);AudioSpecMsg *aud_spec_msg = new  AudioSpecMsg(_profile,_channels,audio_sample_rate);audio_callable_object_(RTMP_BODY_AUD_SPEC, aud_spec_msg, false);     //调用回调,把音频数据返回}

接下来看看音频裸数据发送过来的结果:

// Audio frames
else
{// 每帧都有一个adts// ADTS(7 bytes) + AAC datauint32_t data_len = packet.m_nBodySize - 2 + 7;uint8_t adts[7];//竟然是自己加adtsadts[0] = 0xff;adts[1] = 0xf9;adts[2] = ((_profile - 1) << 6) | (_sample_frequency_index << 2) | (_channels >> 2);adts[3] = ((_channels & 3) << 6) + (data_len >> 11);adts[4] = (data_len & 0x7FF) >> 3;adts[5] = ((data_len & 7) << 5) + 0x1F;adts[6] = 0xfc;// Write audio framesAudioRawMsg *aud_raw = new  AudioRawMsg(data_len);memcpy(aud_raw->data, adts, 7);memcpy(aud_raw->data + 7, packet.m_body + 2, packet.m_nBodySize - 2);    //保存数据-2是要把头去掉if(_audio_pre_pts == -1){_audio_pre_pts= packet.m_nTimeStamp;if(!packet.m_hasAbsTimestamp) {printf("no init video pts\n");}}else {if(packet.m_hasAbsTimestamp)_audio_pre_pts= packet.m_nTimeStamp;else_audio_pre_pts += packet.m_nTimeStamp;}aud_raw->pts = _audio_pre_pts;audio_callable_object_(RTMP_BODY_AUD_RAW, aud_raw, false);
}

还是按视频的老规矩,裸数据发送过来是不带adts头的,所以我们接受的时候需要补上adts头,这样才能符合AAC格式,AAC格式的详解请看以前的文章,这里我就不再过多分析了,就是对应各个位对应的是啥,保存数据的时候-2是要把FLV表示音频的数据减2,这样就是符合AAC的格式了,然后发送给回调函数。

13.6 接收数据回调函数

回调函数就比较简单了,只需要把数据压入队列,然后另外一个线程,音频解码线程,会自动判断队列中是否有数据,这个下节讲。

void PullWork::audioCallback(int what, MsgBaseObj *data, bool flush)
{_audio_decode_loop->post(what, data, flush);//int64_t diff = TimesUtil::GetTimeMillisecond() - cur_time;
//    if(diff>5)
//        LogInfo("audioCallback t:%ld", diff);
}

13.7 音频解码初始化

在上节已经提过了,这个是专门负责音频解码部分的,接下来我们看看这个初始化函数,这个类继承于Looper类,这个类之间有讲过,只要是负责创建一个新的线程,然后在线程中循环检测队列中是否有数据,如果有需要就提取出数据,然后传参给回调函数,回调函数就是我们当前音频解码类,这个等下讲述:

int AudioDecodeLopp::Init(const Properties &properties)
{_aac_decoder = new aacDecoder();      //音频解码类创建if(!_aac_decoder){printf("new AACDecoder() failed\n");return -1;}Properties properties2;if(_aac_decoder->Init(properties2) != 0) //音频解初始化{printf("aac_decoder_ Init failed\n");return -2;}_pcm_buf = new uint8_t[PCM_BUF_MAX_SIZE];   //申请一个buffif(!_pcm_buf){printf("pcm_buf_ new failed");return -3;}return 0;
}

13.8 AAC解码部分

音频解码比较简单:

1.codec = avcodec_find_decoder(AV_CODEC_ID_AAC);        //查找解码器
2.avcodec_alloc_context3(codec);                        //申请解码的数据结构
3.avcodec_open2(ctx, codec, NULL)                       //打开解码器
4.packet = av_packet_alloc();                          //申请一个包
5.frame = av_frame_alloc();                                //申请一帧内存

解码部分:

int aacDecoder::Decode(const uint8_t *in, int inLen, uint8_t *out, int &outLen)
{//If we have inputif (inLen<=0)return -1;//Set datapacket->data = (uint8_t *)in;packet->size = inLen;//Decode itif (avcodec_send_packet(ctx, packet)<0)             //发送一帧数据//nothing{printf("-AACDecoder::Decode() Error decoding AAC packet\n");return -2;}//Release side dataav_packet_free_side_data(packet);                 //释放数据//If we got a frameif (avcodec_receive_frame(ctx, frame)<0)            //接收到解码后的数据//Nothing yet{outLen = 0;return -3;}//Get data//1024float *buffer1 = (float *) frame->data[0];  //  LLLLL float 32bit [-1~1]float *buffer2 = (float *) frame->data[1];  // RRRRRRauto len = frame->nb_samples;int16_t *sample = (int16_t *)out;//Convert to SWORDfor (size_t i=0; i<len; ++i)        //转化陈lrlrlr格式{// lrlrlrsample[i*2] =  (int16_t)(buffer1[i] * 0x7fff);sample[i*2 + 1] =  (int16_t)(buffer2[i] * 0x7fff);}outLen = 4096;static FILE *dump_pcm = NULL;if(!dump_pcm){dump_pcm = fopen("aac_dump.pcm", "wb");if(!dump_pcm){printf("fopen aac_dump.pcm failed\n");}}if(dump_pcm){//ffplay -ar 48000 -ac 2 -f s16le -i aac_dump.pcmfwrite(out, 1,outLen,  dump_pcm);fflush(dump_pcm);}//Return number of samplesreturn 0;
}

13.9 解码线程

上面已经介绍了AAC解码相关模块,虽然已经有了解码函数,但是需要一个驱动源,上面已经介绍过,我们的音频解码模块内部有一个线程,如果队列中有数据的话,就会取出数据,然后调用回调:

void AudioDecodeLopp::handle(int what, MsgBaseObj *data)
{if(what == RTMP_BODY_AUD_SPEC)               //这个是音频配置信息,我们接受到音频配置信息之后,会做音频相关的初始化{AudioSpecMsg *aud_spec = (AudioSpecMsg *)data;// 目前没有做音视频同步,所以现在这里进行音频输出的初始化if(!_audio_out_sdl){//音频初始化}delete aud_spec;}else if(what == RTMP_BODY_AUD_RAW)            //这个是音频的裸数据,接受到数据之后,会直接调用解码函数,解码函数返回了数据之后//可以直接播放{AudioRawMsg *aud_raw = (AudioRawMsg *)data;_pcm_buf_size = PCM_BUF_MAX_SIZE;// 可以发送adts header, 如果不发送adts则要初始化 ctx的参数if(_aac_decoder->Decode(aud_raw->data , aud_raw->size ,_pcm_buf, _pcm_buf_size) == 0){//直接播放}delete aud_raw;     // 要手动释放资源}else{printf("can't handle what:%d", what);delete data;}
}

整个音频的推流拉流就介绍完成了,虽然我也尝试这个代码,感觉bug不少,不过没关系,先能跑起来再说,bug以后再修复,如果一直在修复的话,学习速度就慢,先往后走,这些细节以后再回来修复。

音视频学习(十三、音频数据处理)相关推荐

  1. android音视频工程师,音视频学习 (十三) Android 中通过 FFmpeg 命令对音视频编辑处理(已开源)...

    ## 音视频学习 (十三) Android 中通过 FFmpeg 命令对音视频编辑处理(已开源) ## 视音频编辑器 ## 前言 有时候我们想对音视频进行加工处理,比如视频编辑.添加字幕.裁剪等功能处 ...

  2. Android音视频学习系列(五) — 掌握音频基础知识并使用AudioTrack、OpenSL ES渲染PCM数据

    系列文章 Android音视频学习系列(一) - JNI从入门到精通 Android音视频学习系列(二) - 交叉编译动态库.静态库的入门 Android音视频学习系列(三) - Shell脚本入门 ...

  3. Android音视频学习系列(十) — 基于FFmpeg + OpenSL ES实现音频万能播放器

    系列文章 Android音视频学习系列(一) - JNI从入门到精通 Android音视频学习系列(二) - 交叉编译动态库.静态库的入门 Android音视频学习系列(三) - Shell脚本入门 ...

  4. 音视频学习之 - H264解码

    解码流程 解析数据 (SPS PPS NALU Unit) 初始化解码器 将解析后的H264 NALU Unit输入到解码器 解码完成后回调,输出解码数据 解码数据显示(OpenGL ES) 解析数据 ...

  5. Android音视频 - 学习路线概览

    PS 我们上一个系列 - OpenGL ES 暂告一段落,如果你对相机滤镜感兴趣,可以参看之前的文章. 从本篇开始呢,开始记录Android音视频的相关知识. 学习路线概览 Android音视频的基础 ...

  6. android 键编译,Android 音视频学习系列 (四) 一键编译 32/64 位 FFmpeg 4.2.2

    前言 2020/5/20 增加了硬件解码编译脚本 编译环境 Centos + NDK20b + FFmpeg4.2.2 + Android-21/16 2020/4/26 更新了编译 64 位脚本 编 ...

  7. 音视频 | 音视频学习-01

    音视频 | 音视频学习1 1.说一下播放器的设计过程 这里的话主要分以下几步完成: 开启一个线程进行解封装操作 .读取音频.视频的压缩数据,并进行区分.若视频数据则插入视频队列,音频数据则插入音频队列 ...

  8. 音视频入门系列-音频篇(AAC)

    上篇文章介绍了PCM相关知识,本篇介绍下AAC相关知识. 1.什么是AAC? AAC(Advanced Audio Coding,高级音频编码)是一种声音数据的文件压缩格式.AAC分为ADIF和ADT ...

  9. iOS音视频开发十三:视频渲染,用 Metal 渲染

    本系列文章通过拆解采集 → 编码 → 封装 → 解封装 → 解码 → 渲染流程并实现 Demo 来向大家介绍如何在 iOS/Android 平台上手音视频开发. 这里是第十三篇:iOS 视频渲染 De ...

  10. moviepy音视频开发:音频剪辑基类AudioClip

    ☞ ░ 前往老猿Python博文目录 ░ 一.背景知识介绍 1.1.声音三要素: 音调:人耳对声音高低的感觉称为音调(也叫音频).音调主要与声波的频率有关.声波的频率高,则音调也高. 音量:也就是响度 ...

最新文章

  1. java spring注解教程,spring注解
  2. 增加mysql的sortbuffer_Mysql设置sort_buffer_size
  3. 微信小程序最新开发资源汇总,对学习微信小程序的新手有一定帮助
  4. [CF1036C]Classy Numbers
  5. Linux下git的使用——将已有项目放到github上
  6. spring mvc 提示_Spring BootHibernate提示
  7. 代码在eclipse下不报错,在doc命令行下报错--jar file和runable jar file
  8. 正则表达式表示的IP地址
  9. php中 $_cfg,php 中 get_cfg_var() 与 ini_get() 的异同
  10. socket.io html5 聊天,socket.io实现在线聊天页面
  11. ios人脸照片_基于iOS用CoreImage实现人脸识别
  12. sql分组排序语句顺序
  13. Windows设置电脑每天自动重启
  14. 这一篇带你学点儿 Java8 中的流式数据处理
  15. 计算机知识小口诀,字根表口诀怎么快速背-小学数学:一年级20以内加减法口诀表,附背诵技巧!...
  16. 初尝vue-element-admin
  17. solr常见问题整理
  18. Eclipse汉化方法以及汉化包
  19. Keras模型中数据维度报错
  20. “透视HTTP协议”之破冰篇个人总结

热门文章

  1. 王兴:格局上输了,不管你多努力都不可能赢!
  2. 【VRP】节约里程算法求解车辆路径规划问题【含Matlab源码 1166期】
  3. 利用文本编辑器判断dll/exe是否为64位
  4. Small Tip: 怎么去Schedule一个Analysis for Office的workbook
  5. java中的键值对_java中单个键值对的表示方式
  6. Postman进阶篇(一)-pre-request script入门及实现参数使用随机数
  7. UMG使用UE的取色器/吸色器 SColorPicker
  8. 数字化门店转型| 舞蹈室管理系统| 门店小程序开发教程
  9. Ansys导出刚度矩阵及文件说明
  10. 【WPF】附加事件--《深入浅出WPF》