[文章]基于HarmonyOS Player,实现音频的播放、管理控制和采集

阅读量0
0
4
1. 介绍      
音频播放相关管理应用包括音频播放、声音采集和音频相关管理(快进、快退、暂停、播放、音量控制)等功能,本篇Codelab旨在帮助开发者快速了解HarmonyOS应用开发、播放器应用以及音频收集和系统音量控制的用法。
本篇Codelab将实现的内容
您将构建一个应用程序实现以下内容:
  • 基于HarmonyOS Player播放本地音频资源或从Internet获得的音频资源。
  • 通过音频采集器采集现场音频流并保存到本地,以读取流的方式播放录音。
  • 分别对通知音量、媒体音量、通话音量的控制。
你将会学到什么
  • 如何使用Player播放包括mp3、m4a、aac等主流音频格式音频。
  • 如何使用Player对音频播放控制。
  • 如何使用AudioRecorder采集音频。
  • 如何使用AudioRender播放音频。
  • 如何使用SoundPlayer播放系统短音。
  • 如何使用AudioManager设置通知音量、媒体音量和通话音量。

2. 搭建HarmonyOS环境   
我们首先需要完成HarmonyOS开发环境搭建,可参照如下步骤进行。
  • 安装DevEco Studio,详情请参考下载和安装软件。
  • 设置DevEco Studio开发环境,DevEco Studio开发环境依赖于网络环境,需要连接上网络才能确保工具的正常使用,可以根据如下两种情况来配置开发环境:
    • 如果可以直接访问Internet,只需进行下载HarmonyOS SDK操作。
    • 如果网络不能直接访问Internet,需要通过代理服务器才可以访问,请参考配置开发环境。
  • 开发者可以参考以下链接,完成设备调试的相关配置:
    • 使用真机进行调试
    • 使用模拟器进行调试
3. 代码结构      
本篇Codelab只对核心代码进行讲解,对于完整代码,我们在9 参考章节提供下载方式。接下来我们会讲解整个工程的代码结构,如下图:

  • api:音频播放器及媒体接口文件。
  • constant:代码中使用到的常量。
  • factoty:SourceFactory构建媒体资源。
  • manager:HmPlayerLifecycle处理播放器生命周期。
  • view:PlayerLoading,SimplePlayerController分别为音频加载状态及进度条控制类文件。
  • HmPlayer:封装播放器的主要功能方法。
  • slice:MainAbilitySlice主程序页面和其他相关功能页面。
  • utils:存放所有封装好的公共方法,如DateUtils,LogUtils等。
  • resources:存放工程使用到的资源文件,其中resourcesbaselayout下存放xml布局文件;resourcesbasemedia下存放音频文件。
  • config.json:配置文件,ability声明及权限配置。
   

4. 音频播放器      
音频播放业务逻辑步骤 1 -  创建Player,设置状态回调,HmPlayerCallback继承Player.IPLayerCallback。
  1. Player player = new Player(Context);
  2. player.setPlayerCallback(new HmPlayerCallback());
复制代码

步骤 2 -  准备媒体资源,封装了SourceFactory,根据传入播放源类型。
  1. private void initSourceType(Context context, String path) throws IOException {
  2.     if (context == null || path == null) {
  3.         return;
  4.     }
  5.     if (path.substring(0, NET_HTTP_MATCH.length()).equalsIgnoreCase(NET_HTTP_MATCH)
  6.             || path.substring(0, NET_RTMP_MATCH.length()).equalsIgnoreCase(NET_RTMP_MATCH)
  7.             || path.substring(0, NET_RTSP_MATCH.length()).equalsIgnoreCase(NET_RTSP_MATCH)) {
  8.         mPlayerSource = new Source(path);
  9.     } else if (path.startsWith(STORAGE_MATCH)) {
  10.         File file = new File(path);
  11.         if (file.exists()) {
  12.             FileInputStream fileInputStream = new FileInputStream(file);
  13.             FileDescriptor fileDescriptor = fileInputStream.getFD();
  14.             mPlayerSource = new Source(fileDescriptor);
  15.         }
  16.     } else {
  17.         RawFileDescriptor fd = context.getResourceManager().getRawFileEntry(path).openRawFileDescriptor();
  18.         mPlayerSource = new Source(fd.getFileDescriptor(), fd.getStartPosition(), fd.getFileSize());
  19.     }
  20. }
复制代码

步骤 3 -  播放。
  1. private void start() {
  2.     if (mPlayer != null) {
  3.         mBuilder.mContext.getGlobalTaskDispatcher(TaskPriority.DEFAULT).asyncDispatch(() -> {
  4.             if (surface != null) {
  5.                 mPlayer.setVideoSurface(surface);
  6.             } else {
  7.                 LogUtil.error(TAG, "The surface has not been initialized.");
  8.             }
  9.             mPlayer.prepare();
  10.             if (mBuilder.startMillisecond > 0) {
  11.                 int microsecond = mBuilder.startMillisecond * MICRO_MILLI_RATE;
  12.                 mPlayer.rewindTo(microsecond);
  13.             }
  14.             mPlayer.play();
  15.         });
  16.     }
  17. }
复制代码

步骤 4 -  状态回调,步骤1中设置播放器的状态回调,进度条栏目通过HmPlayer接口addPlayerStatuCallback添加状态回调实现UI更新。
  1. @Override
  2. public void onMediaTimeIncontinuity(Player.MediaTimeInfo mediaTimeInfo) {
  3.     LogUtil.info(TAG, "onMediaTimeIncontinuity is called");
  4.     for (Player.StreamInfo streanInfo : mPlayer.getStreamInfo()) {
  5.         int streamType = streanInfo.getStreamType();
  6.         if (streamType == Player.StreamInfo.MEDIA_STREAM_TYPE_AUDIO && mStatu == PlayerStatu.PREPARED) {
  7.             for (StatuChangeListener callback : statuChangeCallbacks) {
  8.                 mStatu = PlayerStatu.PLAY;
  9.                 callback.statuCallback(PlayerStatu.PLAY);
  10.             }
  11.             if (mBuilder.isPause) {
  12.                 pause();
  13.             }
  14.         }
  15.     }
  16. }

  17. private StatuChangeListener mStatuChangeListener = new StatuChangeListener() {
  18.     [url=home.php?mod=space&uid=2735960]@Override[/url]
  19.     public void statuCallback(PlayerStatu statu) {
  20.         mContext.getUITaskDispatcher().asyncDispatch(() -> {
  21.             switch (statu) {
  22.                 case PREPARING:
  23.                     mPlayToogle.setClickable(false);
  24.                     mProgressBar.setEnabled(false);
  25.                     mProgressBar.setProgressValue(0);
  26.                     break;
  27.                 case PREPARED:
  28.                     mProgressBar.setMaxValue(mPlayer.getDuration());
  29.                     mTotleTime.setText(DateUtils.msToString(mPlayer.getDuration()));
  30.                     break;
  31.                 case PLAY:
  32.                     showController(false);
  33.                     mPlayToogle.setPixelMap(ResourceTable.Media_ic_music_stop);
  34.                     mPlayToogle.setClickable(true);
  35.                     mProgressBar.setEnabled(true);
  36.                     break;
  37.                 case PAUSE:
  38.                     mPlayToogle.setPixelMap(ResourceTable.Media_ic_music_play);
  39.                     break;
  40.                 case STOP:
  41.                 case COMPLETE:
  42.                     mPlayToogle.setPixelMap(ResourceTable.Media_ic_update);
  43.                     mProgressBar.setEnabled(false);
  44.                     break;
  45.                 default:
  46.                     break;
  47.             }
  48.         });
  49.     }
  50. };      
复制代码

使用方法公共业务流程以封装成HmPlayer,开发者可以更快捷方便的使用播放器。
  • 通过HmPlayer.Builder设置播放器参数并使用creater方法构造HmPlayer
    1. player = new HmPlayer.Builder(this).setFilePath(mUrl).create();
    复制代码

  • PlayerModule控件绑定HmPlayer
    1. playerView.bind(player);
    2. playerLoading.bind(player);
    3. controllerView.bind(player);
    复制代码

  • 播放
    1. getGlobalTaskDispatcher(TaskPriority.DEFAULT).delayDispatch(() -> player.play(), Const.NUMBER_100);
    复制代码

  • 同步播放器生命周期到AbilitySlice
    1. @Override
    2. public void onStart(Intent intent) {
    3. super.onStart(intent);
    4. player.getLifecycle().onStart();
    5. }

    6. @Override
    7. public void onForeground(Intent intent) {
    8. player.getLifecycle().onForeground();
    9. super.onForeground(intent);
    10. }

    11. @Override
    12. protected void onBackground() {
    13. player.getLifecycle().onBackground();
    14. super.onBackground();
    15. }

    16. @Override
    17. protected void onStop() {
    18. player.getLifecycle().onStop();
    19. super.onStop();
    20. }
    复制代码

5. 系统短音      
使用方法步骤 1 -  创建SoundPlayer
  1. SoundPlayer soundPlayer = new SoundPlayer();
复制代码
步骤 2 -  初始化系统短音,第一个参数为短音类型,第个二参数为播放时长
  1. soundPlayer.createSound(ToneDescriptor.ToneType.DTMF_0, 500);
复制代码
步骤 3 -  播放
  1. soundPlayer.play();
复制代码
步骤 4 -  释放资源
  1. if (soundPlayer != null) {
  2.     soundPlayer.release();
  3.     soundPlayer = null;
  4. }
复制代码
6. 录音&播放录音      
使用AudioRecorder和AudioRender分别实现录音和播放录音功能,提供了录音、停止录音、播放录音、释放资源等接口,支持录音实时播放和完整录音文件播放模式,按住录音按钮开始录音,松手录音停止并把录音文件保存到本地,点击播放按钮播放录音。
37.png
录音业务逻辑
步骤 1 -  设置录入流相关配置AudioStreamInfo并初始化AudioCapturer以调用底层录音功能
  1. private void initRecord() {
  2.     if (audioStreamInfo == null) {
  3.         audioStreamInfo = new AudioStreamInfo.Builder()
  4.                 .encodingFormat(builder.encodingFormat)
  5.                 .channelMask(builder.channelMask)
  6.                 .sampleRate(builder.inputSampleRate)
  7.                 .build();
  8.         AudioCapturerInfo audioCapturerInfo = new AudioCapturerInfo.Builder()
  9.                 .audioStreamInfo(audioStreamInfo)
  10.                 .audioInputSource(builder.inputSource)
  11.                 .build();
  12.         audioCapturer = new AudioCapturer(audioCapturerInfo);
  13.     }
  14. }
复制代码
步骤 2 -  设置录音保存路径,构建AudioRecorder
  1. savefilePath = getExternalFilesDir(Environment.DIRECTORY_MUSIC) + File.separator + "AudioTest.mp3";
  2. audioRecorder = new AudioRecorder.Builder(this).setSaveFilePath(savefilePath).create();
复制代码
步骤 3 -  使用AudioRecorder开始录音并写入文件
  1. private void beginRecord() {
  2.     if (!audioRecorder.isRecording()) {
  3.         recordTag = isRealTimePlay ? 0 : 1;
  4.         audioRecorder.record();
  5.     }
  6. }
复制代码

播放录音业务逻辑
步骤 1 -  初始化AudioRender设置播放音频流播放格式和
  1. private void initRender() {
  2.     AudioStreamInfo asi = new AudioStreamInfo.Builder()
  3.             .encodingFormat(builder.encodingFormat)
  4.             .channelMask(builder.channelMask)
  5.             .sampleRate(builder.inputSampleRate)
  6.             .audioStreamFlag(builder.streamFlag)
  7.             .streamUsage(builder.streamUsage)
  8.             .build();
  9.     audioRenderInfo = new AudioRendererInfo.Builder()
  10.             .audioStreamInfo(asi)
  11.             .audioStreamOutputFlag(builder.streamOutputFlag)
  12.             .bufferSizeInBytes(bufferSize)
  13.             .isOffload(builder.isOneOffLoad) // false表示分段传输buffer并播放,true表示整个音频流一次性传输到HAL层播放
  14.             .build();
  15.     audioRender = new AudioRenderer(audioRenderInfo, AudioRenderer.PlayMode.MODE_STREAM);
  16. }
复制代码

步骤 2 -  设置音频流播放结束监听器
  1. audioRender.setFrameIntervalObserver(() -> {
  2.     if (audioRender.getAudioTime().getFramePosition() != 0) {
  3.         if (audioPlayListener != null) {
  4.             audioPlayListener.onComplete();
  5.         }
  6.         release();
  7.     }
  8. }, MediaConst.READ_RENDER_INTERVAL, audioRenderHandler);
复制代码

步骤 3 -  启动播放录音并往AudioRender中写入录音流
  1. public void play(byte[] bytes, int length) {
  2.     if (audioRenderInfo == null) {
  3.         initRender();
  4.     }
  5.     start();
  6.     audioRenderHandler.postTask(() -> {
  7.         byte[] datas = new byte[length];
  8.         System.arraycopy(bytes, 0, datas, 0, datas.length);
  9.         audioRender.write(datas, 0, datas.length);
  10.     });
  11. }
复制代码

7. 音量管理      
音量管理功能分为通知音量、媒体音量和通话音量的控制,此项目中点击"音量管理",通过Slider左右滑动设置音量。
38.png
使用方法
步骤 1 -  初始化音量管理者AudioManager

AudioManager audioManager = new AudioManager();
步骤 2 -  根据Slider返回数值设置音量,STREAM_DTMF代表通知音量、STREAM_MUSIC代表媒体音量、STREAM_VOICE_CALL代表通话音量。

  1. @Override
  2. public void onTouchEnd(Slider slider) {
  3.     int progress = slider.getProgress();
  4.     switch (slider.getId()) {
  5.         case ResourceTable.Id_sound_volume_bar:
  6.             try {
  7.                 if (audioManager.setVolume(AudioManager.AudioVolumeType.STREAM_DTMF, progress)) {
  8.                     dtmfVolume = progress;
  9.                 } else {
  10.                     audioManager.setVolume(AudioManager.AudioVolumeType.STREAM_DTMF, dtmfVolume);
  11.                 }
  12.             } catch (SecurityException e) {
  13.                 LogUtil.error(TAG, e.getMessage());
  14.             }
  15.             break;
  16.         case ResourceTable.Id_sound_volume_bar2:
  17.             try {
  18.                 if (audioManager.setVolume(AudioManager.AudioVolumeType.STREAM_MUSIC, progress)) {
  19.                     musicVolume = progress;
  20.                 } else {
  21.                     audioManager.setVolume(AudioManager.AudioVolumeType.STREAM_MUSIC, musicVolume);
  22.                 }
  23.             } catch (SecurityException e) {
  24.                 LogUtil.error(TAG, e.getMessage());
  25.             }
  26.             break;
  27.         case ResourceTable.Id_sound_volume_bar3:
  28.             try {
  29.                 if (audioManager.setVolume(AudioManager.AudioVolumeType.STREAM_VOICE_CALL, progress)) {
  30.                     callVolume = progress;
  31.                 } else {
  32.                     audioManager.setVolume(AudioManager.AudioVolumeType.STREAM_VOICE_CALL, callVolume);
  33.                 }
  34.             } catch (SecurityException e) {
  35.                 LogUtil.error(TAG, e.getMessage());
  36.             }
  37.             break;
  38.         default:
  39.             break;
  40.     }
  41. }
复制代码

8. 恭喜你      目前你已经成功完成了Codelab并且学到了:
  • 使用player播放本地音频资源或从Internet获得的音频资源。
  • 录音功能、保存录音文件和实时播放录音。
  • 系统音量的控制
   

9. 参考      
该项目完整代码您可以在AudioDemoCodelab中下载。
   

回帖

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容图片侵权或者其他问题,请联系本站作侵删。 侵权投诉
链接复制成功,分享给好友