HarmonyOS学习路之开发篇—多媒体开发(音频开发 一)

简介: HarmonyOS音频模块支持音频业务的开发,提供音频相关的功能,主要包括音频播放、音频采集、音量管理和短音播放等。

HarmonyOS音频模块支持音频业务的开发,提供音频相关的功能,主要包括音频播放、音频采集、音量管理和短音播放等。


基本概念

采样

采样是指将连续时域上的模拟信号按照一定的时间间隔采样,获取到离散时域上离散信号的过程。


采样率

采样率为每秒从连续信号中提取并组成离散信号的采样次数,单位用赫兹(Hz)来表示。通常人耳能听到频率范围大约在20Hz~20kHz之间的声音。常用的音频采样频率有:8kHz、11.025kHz、22.05kHz、16kHz、37.8kHz、44.1kHz、48kHz、96kHz、192kHz等。


声道

声道是指声音在录制或播放时在不同空间位置采集或回放的相互独立的音频信号,所以声道数也就是声音录制时的音源数量或回放时相应的扬声器数量。


音频帧

音频数据是流式的,本身没有明确的帧的概念,在实际的应用中,为了音频算法处理/传输的方便,一般约定俗成取2.5ms~60ms为单位的数据量为一帧音频。这个时间被称之为“采样时间”,其长度没有特别的标准,它是根据编解码器和具体应用的需求来决定的。


PCM

PCM(Pulse Code Modulation),即脉冲编码调制,是一种将模拟信号数字化的方法,是将时间连续、取值连续的模拟信号转换成时间离散、抽样值离散的数字信号的过程。


短音

使用源于应用程序包内的资源或者是文件系统里的文件为样本,将其解码成一个16bit单声道或者立体声的PCM流并加载到内存中,这使得应用程序可以直接用压缩数据流同时摆脱CPU加载数据的压力和播放时重解压的延迟。


tone音

根据特定频率生成的波形,比如拨号盘的声音。


系统音

系统预置的短音,比如按键音,删除音等。


约束与限制

在使用完AudioRenderer音频播放类和AudioCapturer音频采集类后,需要调用release()方法进行资源释放。

音频采集所使用的最终采样率与采样格式取决于输入设备,不同设备支持的格式及采样率范围不同,可以通过AudioManager类的getDevices接口查询。

该功能使用需要对应硬件支持,仅支持真机调试。

在进行开发之前,需要申请相关权限,保证应用使用音频相关能力的权限,涉及权限如下表。


image.png

音频播放

场景介绍

音频播放的主要工作是将音频数据转码为可听见的音频模拟信号并通过输出设备进行播放,同时对播放任务进行管理。

接口说明

音频播放类AudioRenderer的主要接口


image.pngimage.png


开发步骤

1. 构造音频流参数的数据结构AudioStreamInfo,推荐使用AudioStreamInfo.Builder类来构造,模板如下,模板中设置的均为AudioStreamInfo.Builder类的默认值,根据音频流的具体规格来设置具体参数。


AudioStreamInfo audioStreamInfo = new AudioStreamInfo.Builder()
    .sampleRate(AudioStreamInfo.SAMPLE_RATE_UNSPECIFIED)
    .audioStreamFlag(AudioStreamInfo.AudioStreamFlag.AUDIO_STREAM_FLAG_NONE)
    .encodingFormat(AudioStreamInfo.EncodingFormat.ENCODING_INVALID)
    .channelMask(AudioStreamInfo.ChannelMask.CHANNEL_INVALID)
    .streamUsage(AudioStreamInfo.StreamUsage.STREAM_USAGE_UNKNOWN)
    .build();

以真实的播放pcm流为例:


AudioStreamInfo audioStreamInfo = new AudioStreamInfo.Builder().sampleRate(44100) // 44.1kHz
    .audioStreamFlag(AudioStreamInfo.AudioStreamFlag.AUDIO_STREAM_FLAG_MAY_DUCK) // 混音
    .encodingFormat(AudioStreamInfo.EncodingFormat.ENCODING_PCM_16BIT) // 16-bit PCM
    .channelMask(AudioStreamInfo.ChannelMask.CHANNEL_OUT_STEREO) // 双声道输出
    .streamUsage(AudioStreamInfo.StreamUsage.STREAM_USAGE_MEDIA) // 媒体类音频
    .build();

2. 使用创建的音频流构建音频播放的参数结构AudioRendererInfo,推荐使用AudioRendererInfo.Builder类来构造,模板如下,模板中设置的均为AudioRendererInfo.Builder类的默认值,根据音频播放的具体规格来设置具体参数。

AudioRendererInfo audioRendererInfo = new AudioRendererInfo.Builder().audioStreamInfo(audioStreamInfo)
    .audioStreamOutputFlag(AudioRendererInfo.AudioStreamOutputFlag.AUDIO_STREAM_OUTPUT_FLAG_NONE)
    .bufferSizeInBytes(0)
    .isOffload(false)
    .sessionID(AudioRendererInfo.SESSION_ID_UNSPECIFIED)
    .build();

以真实的播放pcm流为例:


AudioRendererInfo audioRendererInfo = new AudioRendererInfo.Builder().audioStreamInfo(audioStreamInfo)
    .audioStreamOutputFlag(AudioRendererInfo.AudioStreamOutputFlag.AUDIO_STREAM_OUTPUT_FLAG_DIRECT_PCM) // pcm格式的输出流
    .bufferSizeInBytes(100)
    .isOffload(false) // false表示分段传输buffer并播放,true表示整个音频流一次性传输到HAL层播放
    .build();

3. 根据要播放音频流指定PlayMode,不同的PlayMode在写数据时存在差异,详情见步骤7,其余播放流程是无区别的。并通过构造函数获取AudioRenderer类的实例化对象。


4. 使用构造函数获取AudioRenderer类的实例化对象,其中步骤2、步骤3中的数据为构造函数的必选参数,指定播放设备为可选参数,根据使用场景选择不同的构造函数。


5. (可选)构造音频播放回调,首先构造对象AudioInterrupt,其中setInterruptListener方法的入参需要实现接口类InterruptListener,setStreamInfo方法使用步骤1的AudioStreamInfo作为入参。然后调用AudioManager类的activateAudioInterrupt(AudioInterrupt interrupt)方法进行音频播放回调注册。代码示例如下:

AudioRenderer renderer = new AudioRenderer(audioRendererInfo, AudioRenderer.PlayMode.MODE_STREAM);
AudioInterrupt audioInterrupt = new AudioInterrupt();
AudioManager audioManager = new AudioManager();
audioInterrupt.setStreamInfo(audioStreamInfo);
audioInterrupt.setInterruptListener(new AudioInterrupt.InterruptListener() {
    @Override
    public void onInterrupt(int type, int hint) {
        if (type == AudioInterrupt.INTERRUPT_TYPE_BEGIN
                && hint == AudioInterrupt.INTERRUPT_HINT_PAUSE) {
            renderer.pause();
        } else if (type == AudioInterrupt.INTERRUPT_TYPE_BEGIN
                && hint == AudioInterrupt.INTERRUPT_HINT_NONE) {
        } else if (type == AudioInterrupt.INTERRUPT_TYPE_END && (
                hint == AudioInterrupt.INTERRUPT_HINT_NONE
                        || hint == AudioInterrupt.INTERRUPT_HINT_RESUME)) {
            renderer.start();
        } else {
            HiLog.warn(TAG, "unexpected type or hint");
        }
    }
});
audioManager.activateAudioInterrupt(audioInterrupt);


6. 调用AudioRenderer实例化对象的start()方法启动播放任务


AudioRenderer renderer = new AudioRenderer(audioRendererInfo, AudioRenderer.PlayMode.MODE_STREAM);
renderer.start();

7. 将要播放的音频数据读取为byte流或short流,对于选择MODE_STREAM模式的PlayMode,需要循环调用write方法进行数据写入。对于选择MODE_STATIC模式的PlayMode,只能通过调用一次write方法将要播放的音频数据全部写入,因此该模式限制在文件规格较小的音频数据播放场景下才能使用

AudioRenderer renderer = new AudioRenderer(audioRendererInfo, AudioRenderer.PlayMode.MODE_STREAM);
String Path = "resources/***/***.pcm"; // 自定义pcm文件
BufferedInputStream bis1 = null;
try {
    RawFileDescriptor rawFileDescriptor = getResourceManager().getRawFileEntry(Path).openRawFileDescriptor();
    FileDescriptor fileDescriptor1 = rawFileDescriptor.getFileDescriptor();
    bis1 = new BufferedInputStream(new FileInputStream(fileDescriptor1));
    int minBufferSize = renderer.getMinBufferSize(44100, AudioStreamInfo.EncodingFormat.ENCODING_PCM_16BIT,  
    AudioStreamInfo.ChannelMask.CHANNEL_OUT_STEREO);
    byte[] buffers = new byte[minBufferSize];
    while ((bis1.read(buffers)) != -1) {
    boolean write1 = renderer.write(buffers, 0, buffers.length);
    renderer.flush();
    }
    } catch (Exception e) {
    e.printStackTrace();
    } finally {
    if (bis1!=null){
       try {
        bis1.close();
           } catch (IOException e) {
        e.printStackTrace();
        }
    }
   }
BufferedInputStream bis2 = null;
try {
    RawFileDescriptor rawFileDescriptor = getResourceManager().getRawFileEntry(Path).openRawFileDescriptor();
    FileDescriptor fileDescriptor1 = rawFileDescriptor.getFileDescriptor();
    bis2 = new BufferedInputStream(new FileInputStream(fileDescriptor1));
    int minBufferSize = renderer.getMinBufferSize(44100, AudioStreamInfo.EncodingFormat.ENCODING_PCM_16BIT,  
    AudioStreamInfo.ChannelMask.CHANNEL_OUT_STEREO);
    byte[] buffers = new byte[minBufferSize];
    int len ;
    while ((len = bis2.read(buffers)) != -1) {
    short[] shorts = new short[len];
    boolean write2 = renderer.write(shorts, 0, shorts.length);
    renderer.flush();
    }
    } catch (Exception e) {
    e.printStackTrace();
    } finally {
    if (bis2!=null){
       try {
        bis2.close();
           } catch (IOException e) {
        e.printStackTrace();
        }
    }
   }
AudioRenderer renderer1 = new AudioRenderer(audioRendererInfo, AudioRenderer.PlayMode.MODE_STATIC);
String Path1 = "resources/***/***.pcm";
BufferedInputStream bis3 = null;
try {
   RawFileDescriptor rawFileDescriptor = getResourceManager().getRawFileEntry(Path1).openRawFileDescriptor();
   FileDescriptor fileDescriptor1 = rawFileDescriptor.getFileDescriptor();
   bis3 = new BufferedInputStream(new FileInputStream(fileDescriptor1));
   byte[] bytes = new byte[bis3.available()];
   boolean write3 = renderer1.write(bytes, 0, bytes.length);
   } catch (IOException e) {
     e.printStackTrace();
   }finally {
    if (bis3!=null){
        try {
         bis3.close();
        } catch (IOException e) {
        e.printStackTrace();
        }
       }
  }
BufferedInputStream bis4 = null;
try {
   RawFileDescriptor rawFileDescriptor = getResourceManager().getRawFileEntry(Path1).openRawFileDescriptor();
   FileDescriptor fileDescriptor1 = rawFileDescriptor.getFileDescriptor();
   bis4 = new BufferedInputStream(new FileInputStream(fileDescriptor1));
   short[] shorts = new short[bis4.available()];
   boolean write4 = renderer1.write(shorts, 0, shorts.length);
   } catch (IOException e) {
     e.printStackTrace();
   }finally {
    if (bis4!=null){
        try {
         bis4.close();
        } catch (IOException e) {
        e.printStackTrace();
        }
       }
  }

8. (可选)当需要对音频播放进行暂停或停止时,调用AudioRenderer实例化对象的pause()或stop()方法进行暂停或停止播放。

AudioRenderer renderer = new AudioRenderer(audioRendererInfo, AudioRenderer.PlayMode.MODE_STREAM);
renderer.pause();

9. (可选)调用AudioRenderer实例化对象的setSpeed调节播放速度,setVolume调节播放音量。


renderer.setSpeed(0.5f);
renderer.setVolume(0.5f);
renderer.stop();

10. 播放任务结束后,调用AudioRenderer实例化对象的release()释放资源。


renderer.release();


相关文章
|
3天前
鸿蒙开发:了解@Builder装饰器
@Builder装饰是鸿蒙UI开发中,非常重要的一个装饰器,在实际的开发中,合理且正确的使用,能够让我们的代码更加的简洁,有两点需要注意,一是,是用私有还是全局,取决于当前的组件的复用机制,如果多个页面都使用了,建议以全局为主;二是传参的动态更新,有更新就使用引用参数传递,没有更新按值传递即可。
50 28
|
1月前
|
存储 JSON 区块链
【HarmonyOS NEXT开发——ArkTS语言】购物商城的实现【合集】
HarmonyOS应用开发使用@Component装饰器将Home结构体标记为一个组件,意味着它可以在界面构建中被当作一个独立的UI单元来使用,并且按照其内部定义的build方法来渲染具体的界面内容。txt:string定义了一个名为Data的接口,用于规范表示产品数据的结构。src:类型为,推测是用于引用资源(可能是图片资源等)的一种特定类型,用于指定产品对应的图片资源。txt:字符串类型,用于存放产品的文字描述,比如产品名称等相关信息。price:数值类型,用于表示产品的价格信息。
61 5
|
1月前
|
开发工具 开发者 容器
【HarmonyOS NEXT开发——ArkTS语言】欢迎界面(启动加载页)的实现【合集】
从ArkTS代码架构层面而言,@Entry指明入口、@Component助力复用、@Preview便于预览,只是初窥门径,为开发流程带来些许便利。尤其动画回调与Blank组件,细节粗糙,后续定当潜心钻研,力求精进。”,字体颜色为白色,字体大小等设置与之前类似,不过动画配置有所不同,时长为。,不过这里没有看到额外的动画效果添加到这个特定的图片元素上(与前面带动画的元素对比而言)。这是一个显示文本的视图,文本内容为“奇怪的知识”,设置了字体颜色为灰色(的结构体,它代表了整个界面组件的逻辑和视图结构。
55 1
|
1月前
|
存储 人工智能 JavaScript
Harmony OS开发-ArkTS语言速成二
本文介绍了ArkTS基础语法,包括三种基本数据类型(string、number、boolean)和变量的使用。重点讲解了let、const和var的区别,涵盖作用域、变量提升、重新赋值及初始化等方面。期待与你共同进步!
112 47
Harmony OS开发-ArkTS语言速成二
|
2月前
|
缓存 API 数据安全/隐私保护
自学记录:学习HarmonyOS Location Kit构建智能定位服务
作为一名对新技术充满好奇心的开发者,我选择了HarmonyOS Next 5.0.1(API 13)作为挑战对象,深入研究其强大的定位服务API——Location Kit。从权限管理、获取当前位置、逆地理编码到地理围栏,最终成功开发了一款智能定位应用。本文将结合代码和开发过程,详细讲解如何实现这些功能,并分享遇到的挫折与兴奋时刻。希望通过我的经验,能帮助其他开发者快速上手HarmonyOS开发,共同探索更多可能性。
135 5
|
2月前
|
前端开发 API 数据库
鸿蒙开发:异步并发操作
在结合async/await进行使用的时候,有一点需要注意,await关键字必须结合async,这两个是搭配使用的,缺一不可,同步风格在使用的时候,如何获取到错误呢,毕竟没有catch方法,其实,我们可以自己创建try/catch来捕获异常。
105 3
鸿蒙开发:异步并发操作
|
2月前
|
API
鸿蒙开发:实现popup弹窗
目前提供了两种方式实现popup弹窗,主推系统实现的方式,几乎能满足我们常见的所有场景,当然了,文章毕竟有限,尽量还是以官网为主。
106 2
鸿蒙开发:实现popup弹窗
|
2月前
|
开发框架 物联网 API
HarmonyOS开发:串行通信开发详解
在电子设备和智能系统的设计中,数据通信是连接各个组件和设备的核心,串行通信作为一种基础且广泛应用的数据传输方式,因其简单、高效和成本效益高而被广泛采用。HarmonyOS作为一个全场景智能终端操作系统,不仅支持多种设备和场景,还提供了强大的开发框架和API,使得开发者能够轻松实现串行通信功能。随着技术的不断进步,串行通信技术也在不断发展。在HarmonyOS中,串行通信的开发不仅涉及到基本的数据发送和接收,还包括设备配置、错误处理和性能优化等多个方面。那么本文就来深入探讨在HarmonyOS中如何开发串行通信应用,包括串行通信的基础知识、HarmonyOS提供的API、开发步骤和实际代码示例
61 2
|
2月前
鸿蒙语言开发 几十套鸿蒙ArkTs app毕业设计及课程作业
鸿蒙语言开发 几十套鸿蒙ArkTs app毕业设计及课程作业
53 1
|
2月前
|
API
鸿蒙开发:自定义一个英文键盘
实现方式呢,有很多种,目前采用了比较简单的一种,如果大家采用网格Grid组件实现方式,也是可以的,但是需要考虑每行的边距以及数据,还有最后两行的格子占位问题。
鸿蒙开发:自定义一个英文键盘

热门文章

最新文章