android 音频采集、FLTP重采样与AAC编码推流

2024-03-16 00:18

本文主要是介绍android 音频采集、FLTP重采样与AAC编码推流,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

相比较视频编码,音频编码要简单很多,主要就是将采集到的音频源数据PCM编码AAC.
MediaPlus中FFmpeg使用的是libfdk-aac编码器,这里有个问题需要注意下:FFmpeg已经废弃了AV_SAMPLE_FMT_S16格式PCM编码AAC,也就是说如果使用FFmpeg自带的AAC编码器,必须做音频的重采样(重采样为:AV_SAMPLE_FMT_FLTP),否则AAC编码是失败的。

接下来,看下MediaPlus中是如何采集音频与AAC编码的.

在app.mobile.nativeapp.com.libmedia.core.streamer.RtmpPushStreamer的AudioThread获取AudioRecord采集到的音频数据并传入底层:

  class AudioThread extends Thread {public volatile boolean m_bExit = false;@Overridepublic void run() {// TODO Auto-generated method stubsuper.run();int[] dataLength;byte[] audioBuffer;AudioCaptureInterface.GetAudioDataReturn ret;dataLength = new int[1];audioBuffer = new byte[m_aiBufferLength[0]];while (!m_bExit) {try {Thread.sleep(1, 10);if (m_bExit) {break;}} catch (InterruptedException e) {e.printStackTrace();}try {ret = mAudioCapture.GetAudioData(audioBuffer,m_aiBufferLength[0], dataLength);if (ret == AudioCaptureInterface.GetAudioDataReturn.RET_SUCCESS) {encodeAudio(audioBuffer, dataLength[0]);}} catch (Exception e) {e.printStackTrace();stopThread();}}}

具体AudioRecord采集具体实现是avcapture.jar包中,代码比较简单,相关android音视频采集初始化及API调用网上都有相关Demo,这里不再赘述!

  • encodeAudio(audioBuffer, dataLength[0]);将音频数据传入底层。
      /*** 采集的PCM音频数据** @param audioBuffer* @param length*/public void encodeAudio(byte[] audioBuffer, int length) {try {LiveJniMediaManager.EncodeAAC(audioBuffer, length);} catch (Exception e) {e.printStackTrace();}}
  • JNI层接收到PCM音频数据,添加到AudioCapture同步队列中:
    JNIEXPORT jint JNICALL
    Java_app_mobile_nativeapp_com_libmedia_core_jni_LiveJniMediaManager_EncodeAAC(JNIEnv *env,jclass type,jbyteArray audioBuffer_,jint length) {if (audioCaptureInit && !isClose) {jbyte *audioSrc = env->GetByteArrayElements(audioBuffer_, 0);uint8_t *audioDstData = (uint8_t *) malloc(length);memcpy(audioDstData, audioSrc, length);OriginData *audioOriginData = new OriginData();audioOriginData->size = length;audioOriginData->data = audioDstData;audioCapture->PushAudioData(audioOriginData);env->ReleaseByteArrayElements(audioBuffer_, audioSrc, 0);}return 0;
    }
    以上代码,是在调用app.mobile.nativeapp.com.libmedia.core.streamer.RtmpPushStreamer>>startPushStream()开启推流前的相关调用:主要就是初始化音频采集,并将数据传入底层。
  • startPushStream的调用,会重置AudioCapture::ExitCapture=false;
    数据才会被加入到audioCaputureframeQueue对列中.
    /*** 开启推流* @param pushUrl* @return*/private boolean startPushStream(String pushUrl) {if (nativeInt) {int ret = 0;ret = LiveJniMediaManager.StartPush(pushUrl);if (ret < 0) {Log.d("initNative", "native push failed!");return false;}return true;}return false;}
    如下图:

  • 重置标记后,audioCaputureframeQueue.push将数据添中到队列中.

int AudioCapture::PushAudioData(OriginData *originData) {if (ExitCapture) {return 0;}originData->pts = av_gettime();LOG_D(DEBUG,"audio capture pts :%lld",originData->pts);audioCaputureframeQueue.push(originData);return 0;
}

上面这些代码与视频的处理方式都是一样的流程,在调用app.mobile.nativeapp.com.libmedia.core.streamer.RtmpPushStreamer>>startPushStream(),已经开始往音频队列中添加数据,紧接着调用rtmpStreamer->StartPushStream() ,实际也就是开启了音视频的两个编码线程及推流,推流相关代码与视频一致.


int RtmpStreamer::StartPushStream() {videoStreamIndex = AddStream(videoEncoder->videoCodecContext);audioStreamIndex = AddStream(audioEncoder->audioCodecContext);pthread_create(&t3, NULL, RtmpStreamer::WriteHead, this);pthread_join(t3, NULL);VideoCapture *pVideoCapture = videoEncoder->GetVideoCapture();AudioCapture *pAudioCapture = audioEncoder->GetAudioCapture();pVideoCapture->videoCaputureframeQueue.clear();pAudioCapture->audioCaputureframeQueue.clear();if(writeHeadFinish) {pthread_create(&t1, NULL, RtmpStreamer::PushAudioStreamTask, this);pthread_create(&t2, NULL, RtmpStreamer::PushVideoStreamTask, this);}else{return -1;}return 0;
}
  • PushAudioStreamTask中从队列中获取数据编码、推流.
    rtmpStreamer->audioEncoder->EncodeAAC(&pAudioData);AAC编码.
    rtmpStreamer->SendFrame(pAudioData, rtmpStreamer->audioStreamIndex);推流(与视频推流一致)

这里说明下,音频编码前获取编码器及一些参数的指定:
libmedia/src/main/cpp/AudioEncoder.cpp是音频编码的核心类,int AudioEncoder::InitEncode() 方法封装了音频编码器的初始化。

int AudioEncoder::InitEncode() {std::lock_guard<std::mutex> lk(mut);avCodec = avcodec_find_encoder_by_name("libfdk_aac");int ret = 0;if (!avCodec) {LOG_D(DEBUG, "aac encoder not found!")return -1;}audioCodecContext = avcodec_alloc_context3(avCodec);if (!audioCodecContext) {LOG_D(DEBUG, "avcodec alloc context3 failed!");return -1;}audioCodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;audioCodecContext->sample_fmt = AV_SAMPLE_FMT_S16;audioCodecContext->sample_rate = audioCapture->GetAudioEncodeArgs()->sampleRate;audioCodecContext->thread_count = 8;audioCodecContext->bit_rate = 50*1024*8;audioCodecContext->channels = audioCapture->GetAudioEncodeArgs()->channels;audioCodecContext->frame_size = audioCapture->GetAudioEncodeArgs()->nb_samples;audioCodecContext->time_base = {1, 1000000};//AUDIO VIDEO 两边时间基数要相同audioCodecContext->channel_layout = av_get_default_channel_layout(audioCodecContext->channels);outputFrame = av_frame_alloc();outputFrame->channels = audioCodecContext->channels;outputFrame->channel_layout = av_get_default_channel_layout(outputFrame->channels);outputFrame->format = audioCodecContext->sample_fmt;outputFrame->nb_samples = 1024;ret = av_frame_get_buffer(outputFrame, 0);if (ret != 0) {LOG_D(DEBUG, "av_frame_get_buffer failed!");return -1;}LOG_D(DEBUG, "av_frame_get_buffer success!");ret = avcodec_open2(audioCodecContext, NULL, NULL);if (ret != 0) {char buf[1024] = {0};av_strerror(ret, buf, sizeof(buf));LOG_D(DEBUG, "avcodec open failed! info:%s", buf);return -1;}LOG_D(DEBUG, "open audio codec success!");LOG_D(DEBUG, "Complete init Audio Encode!")return 0;
}
  • 指定获取libfdk_aac编码器
    avCodec = avcodec_find_encoder_by_name("libfdk_aac");
  • 初始化编码器上下文
    audioCodecContext = avcodec_alloc_context3(avCodec);
  • 创建AVFrame,并分配内存负责封装PCM源数据

    outputFrame = av_frame_alloc();outputFrame->channels = audioCodecContext->channels;//通道数outputFrame->channel_layout = av_get_default_channel_layout(outputFrame->channels);outputFrame->format = audioCodecContext->sample_fmt;outputFrame->nb_samples = 1024;//默认值ret = av_frame_get_buffer(outputFrame, 0);if (ret != 0) {LOG_D(DEBUG, "av_frame_get_buffer failed!");return -1;}LOG_D(DEBUG, "av_frame_get_buffer success!");
  • 打开编码器

    ret = avcodec_open2(audioCodecContext, NULL, NULL);

    以上是编码前必须要完成的初始化.

int AudioEncoder::EncodeAAC 方法封装了AAC编码:

int AudioEncoder::EncodeAAC(OriginData **originData) {int ret = 0;ret = avcodec_fill_audio_frame(outputFrame,audioCodecContext->channels,audioCodecContext->sample_fmt, (*originData)->data,8192, 0);outputFrame->pts = (*originData)->pts;ret = avcodec_send_frame(audioCodecContext, outputFrame);if (ret != 0) {
#ifdef SHOW_DEBUG_INFOLOG_D(DEBUG, "send frame failed!");
#endif}av_packet_unref(&audioPacket);ret = avcodec_receive_packet(audioCodecContext, &audioPacket);if (ret != 0) {
#ifdef SHOW_DEBUG_INFOLOG_D(DEBUG, "receive packet failed!");
#endif}(*originData)->Drop();(*originData)->avPacket = &audioPacket;#ifdef SHOW_DEBUG_INFOLOG_D(DEBUG, "encode audio packet size:%d pts:%lld", (*originData)->avPacket->size,(*originData)->avPacket->pts);LOG_D(DEBUG, "Audio frame encode success!");
#endif(*originData)->avPacket->size;return audioPacket.size;
}
  • *originData->data填充到AVFrame中,
    ret = avcodec_send_frame(audioCodecContext, outputFrame);
    ret = avcodec_receive_packet(audioCodecContext, &audioPacket);
    audioPacket就是编码后的数据了,data是编码后的数据,size是大小,这样就完成了编码.

注意:在int AudioEncoder::InitEncode()方法中

avcodec_find_encoder_by_name("libfdk_aac");

这里使用了fdk-aac编码器,前提是你必须要将libfdk-aac库,链接到ffmpeg动态库中,否则是找不到此编码器的。FFmpeg自带有AAC编码器,可以通过:

 avcodec_find_encoder(AV_CODEC_ID_AAC);

获取到AAC编码器,当然如果使用FFmpeg的AAC编码器,就会涉及到一个问题,就是刚开始文中提到了,AV_SAMPLE_FMT_S16需要重采样为:AV_SAMPLE_FMT_FLTP的问题,由于FFmpeg废弃了AV_SAMPLE_FMT_S16格式PCM编码AAC,那么在编码前就需要多一步重采样的处理.

以下AV_SAMPLE_FMT_S16 PCM音频数据重采样相关代码仅供参考:
  • 初始化SwrContext,指定输入输出参数

      swrContext = swr_alloc_set_opts(swrContext, av_get_default_channel_layout(CHANNELS),//输出通道LayoutAV_SAMPLE_FMT_FLTP,//输出格式48000,//输出采样率av_get_default_channel_layout(CHANNELS),//输入通道LayoutAV_SAMPLE_FMT_S16,//输入格式48000,//输入采样率NULL,//NULLNULL);//NULLret = swr_init(swrContext);//初始化SwrContextif (ret != 0) {LOG_D(DEBUG, "swr_init failed!");return -1;}
  • AAC编码前,将源数据重采样

     for (; ;) {if (encodeAAC->exit) {break;}if (encodeAAC->frame_queue.empty()) {continue;}const uint8_t *indata[AV_NUM_DATA_POINTERS] = {0};//PCM s16uint8_t *buf = *encodeAAC->frame_queue.wait_and_pop().get();//PCM 16bit
    #ifdef FDK_CODEC//fdk-aac无需重采样ret = avcodec_fill_audio_frame(encodeAAC->outputFrame, encodeAAC->avCodecContext->channels,encodeAAC->avCodecContext->sample_fmt, buf, BUFFER_SIZE, 0);if (ret < 0) {LOG_D(DEBUG, "fill frame failed!");continue;}
    #else//重采样AM_SAMPLE_FMT_FLTPindata[0] = buf;swr_convert(encodeAAC->swrContext, encodeAAC->outputFrame->data,encodeAAC->outputFrame->nb_samples, indata,encodeAAC->outputFrame->nb_samples);
    #endif
    以上代码就可以实现音频重采样,这样就可以再使用FFMPEG AAC编码器完成编码.

以上简述了android 采集音频PCM数据及AAC编码、AAC编码涉及的相关初始化、FFmpeg AAC编码器的重采样示例.android camera采集、H264编码与Rtmp推流与本文描述了音视频采集、编码过程及如何完成推流,相关文章待续......


作者:swordman
链接:https://juejin.im/post/5a1b6bdbf265da43040654a6
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

这篇关于android 音频采集、FLTP重采样与AAC编码推流的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/813737

相关文章

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

android-opencv-jni

//------------------start opencv--------------------@Override public void onResume(){ super.onResume(); //通过OpenCV引擎服务加载并初始化OpenCV类库,所谓OpenCV引擎服务即是 //OpenCV_2.4.3.2_Manager_2.4_*.apk程序包,存

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动

C++ | Leetcode C++题解之第393题UTF-8编码验证

题目: 题解: class Solution {public:static const int MASK1 = 1 << 7;static const int MASK2 = (1 << 7) + (1 << 6);bool isValid(int num) {return (num & MASK2) == MASK1;}int getBytes(int num) {if ((num &

C语言 | Leetcode C语言题解之第393题UTF-8编码验证

题目: 题解: static const int MASK1 = 1 << 7;static const int MASK2 = (1 << 7) + (1 << 6);bool isValid(int num) {return (num & MASK2) == MASK1;}int getBytes(int num) {if ((num & MASK1) == 0) {return

Android 10.0 mtk平板camera2横屏预览旋转90度横屏拍照图片旋转90度功能实现

1.前言 在10.0的系统rom定制化开发中,在进行一些平板等默认横屏的设备开发的过程中,需要在进入camera2的 时候,默认预览图像也是需要横屏显示的,在上一篇已经实现了横屏预览功能,然后发现横屏预览后,拍照保存的图片 依然是竖屏的,所以说同样需要将图片也保存为横屏图标了,所以就需要看下mtk的camera2的相关横屏保存图片功能, 如何实现实现横屏保存图片功能 如图所示: 2.mtk

android应用中res目录说明

Android应用的res目录是一个特殊的项目,该项目里存放了Android应用所用的全部资源,包括图片、字符串、颜色、尺寸、样式等,类似于web开发中的public目录,js、css、image、style。。。。 Android按照约定,将不同的资源放在不同的文件夹中,这样可以方便的让AAPT(即Android Asset Packaging Tool , 在SDK的build-tools目

Android fill_parent、match_parent、wrap_content三者的作用及区别

这三个属性都是用来适应视图的水平或者垂直大小,以视图的内容或尺寸为基础的布局,比精确的指定视图的范围更加方便。 1、fill_parent 设置一个视图的布局为fill_parent将强制性的使视图扩展至它父元素的大小 2、match_parent 和fill_parent一样,从字面上的意思match_parent更贴切一些,于是从2.2开始,两个属性都可以使用,但2.3版本以后的建议使

Android Environment 获取的路径问题

1. 以获取 /System 路径为例 /*** Return root of the "system" partition holding the core Android OS.* Always present and mounted read-only.*/public static @NonNull File getRootDirectory() {return DIR_ANDR