• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 开发音频通话功能
2
3在音频通话场景下,音频输出(播放对端声音)和音频输入(录制本端声音)会同时进行,应用可以通过使用AudioRenderer来实现音频输出,通过使用AudioCapturer来实现音频输入,同时使用AudioRenderer和AudioCapturer即可实现音频通话功能。
4
5在音频通话开始和结束时,应用可以自行检查当前的[音频场景模式](audio-call-overview.md#音频场景模式)和[铃声模式](audio-call-overview.md#铃声模式),以便采取合适的音频管理及提示策略。
6
7以下代码示范了同时使用AudioRenderer和AudioCapturer实现音频通话功能的基本过程,其中未包含音频通话数据的传输过程,实际开发中,需要将网络传输来的对端通话数据解码播放,此处仅以读取音频文件的数据代替;同时需要将本端录制的通话数据编码打包,通过网络发送给对端,此处仅以将数据写入音频文件代替。
8
9## 使用AudioRenderer播放对端的通话声音
10
11  该过程与[使用AudioRenderer开发音频播放功能](using-audiorenderer-for-playback.md)过程相似,关键区别在于audioRendererInfo参数和音频数据来源。audioRendererInfo参数中,音频内容类型需设置为语音:CONTENT_TYPE_SPEECH,音频流使用类型需设置为VOIP通话:STREAM_USAGE_VOICE_COMMUNICATION。
12
13```ts
14import { audio } from '@kit.AudioKit';
15import { fileIo as fs } from '@kit.CoreFileKit';
16import { BusinessError } from '@kit.BasicServicesKit';
17import { common } from '@kit.AbilityKit';
18
19const TAG = 'VoiceCallDemoForAudioRenderer';
20// 与使用AudioRenderer开发音频播放功能过程相似,关键区别在于audioRendererInfo参数和音频数据来源。
21class Options {
22  offset?: number;
23  length?: number;
24}
25
26let bufferSize: number = 0;
27let renderModel: audio.AudioRenderer | undefined = undefined;
28let audioStreamInfo: audio.AudioStreamInfo = {
29  samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // 采样率。
30  channels: audio.AudioChannel.CHANNEL_2, // 通道。
31  sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式。
32  encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式。
33};
34let audioRendererInfo: audio.AudioRendererInfo = {
35  // 需使用通话场景相应的参数。
36  usage: audio.StreamUsage.STREAM_USAGE_VOICE_COMMUNICATION, // 音频流使用类型:VOIP通话。
37  rendererFlags: 0 // 音频渲染器标志:默认为0即可。
38};
39let audioRendererOptions: audio.AudioRendererOptions = {
40  streamInfo: audioStreamInfo,
41  rendererInfo: audioRendererInfo
42};
43// 请在组件内获取context,确保this.getUIContext().getHostContext()返回结果为UIAbilityContext。
44let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
45let path = context.cacheDir;
46// 确保该沙箱路径下存在该资源。
47let filePath = path + '/StarWars10s-2C-48000-4SW.wav';
48let file: fs.File = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
49let writeDataCallback = (buffer: ArrayBuffer) => {
50  let options: Options = {
51    offset: bufferSize,
52    length: buffer.byteLength
53  };
54  fs.readSync(file.fd, buffer, options);
55  bufferSize += buffer.byteLength;
56};
57
58// 初始化,创建实例,设置监听事件。
59audio.createAudioRenderer(audioRendererOptions, (err: BusinessError, renderer: audio.AudioRenderer) => { // 创建AudioRenderer实例。
60  if (!err) {
61    console.info(`${TAG}: creating AudioRenderer success`);
62    renderModel = renderer;
63    if (renderModel !== undefined) {
64      renderModel.on('stateChange', (state: audio.AudioState) => { // 设置监听事件,当转换到指定的状态时触发回调。
65        if (state == 1) {
66          console.info('audio renderer state is: STATE_PREPARED');
67        }
68        if (state == 2) {
69          console.info('audio renderer state is: STATE_RUNNING');
70        }
71      });
72      renderModel.on('markReach', 1000, (position: number) => { // 订阅markReach事件,当渲染的帧数达到1000帧时触发回调。
73        if (position == 1000) {
74          console.info('ON Triggered successfully');
75        }
76      });
77      renderModel.on('writeData', writeDataCallback);
78    }
79  } else {
80    console.info(`${TAG}: creating AudioRenderer failed, error: ${err.message}`);
81  }
82});
83
84// 开始一次音频渲染。
85async function start() {
86  if (renderModel !== undefined) {
87    let stateGroup: number[] = [audio.AudioState.STATE_PREPARED, audio.AudioState.STATE_PAUSED, audio.AudioState.STATE_STOPPED];
88    if (stateGroup.indexOf(renderModel.state.valueOf()) === -1) { // 当且仅当状态为STATE_PREPARED、STATE_PAUSED和STATE_STOPPED之一时才能启动渲染。
89      console.error(TAG + 'start failed');
90      return;
91    }
92    renderModel.start((err: BusinessError) => {
93      if (err) {
94        console.error('Renderer start failed.');
95      } else {
96        console.info('Renderer start success.');
97      }
98    });
99  }
100}
101
102// 暂停渲染。
103async function pause() {
104  if (renderModel !== undefined) {
105    // 只有渲染器状态为STATE_RUNNING的时候才能暂停。
106    if (renderModel.state.valueOf() !== audio.AudioState.STATE_RUNNING) {
107      console.info('Renderer is not running');
108      return;
109    }
110    await renderModel.pause(); // 暂停渲染。
111    if (renderModel.state.valueOf() === audio.AudioState.STATE_PAUSED) {
112      console.info('Renderer is paused.');
113    } else {
114      console.error('Pausing renderer failed.');
115    }
116  }
117}
118
119// 停止渲染。
120async function stop() {
121  if (renderModel !== undefined) {
122    // 只有渲染器状态为STATE_RUNNING或STATE_PAUSED的时候才可以停止。
123    if (renderModel.state.valueOf() !== audio.AudioState.STATE_RUNNING && renderModel.state.valueOf() !== audio.AudioState.STATE_PAUSED) {
124      console.info('Renderer is not running or paused.');
125      return;
126    }
127    await renderModel.stop(); // 停止渲染。
128    if (renderModel.state.valueOf() === audio.AudioState.STATE_STOPPED) {
129      console.info('Renderer stopped.');
130    } else {
131      console.error('Stopping renderer failed.');
132    }
133  }
134}
135
136// 销毁实例,释放资源。
137async function release() {
138  if (renderModel !== undefined) {
139    // 渲染器状态不是STATE_RELEASED状态,才能release。
140    if (renderModel.state.valueOf() === audio.AudioState.STATE_RELEASED) {
141      console.info('Renderer already released');
142      return;
143    }
144    await renderModel.release(); // 释放资源。
145    if (renderModel.state.valueOf() === audio.AudioState.STATE_RELEASED) {
146      console.info('Renderer released');
147    } else {
148      console.error('Renderer release failed.');
149    }
150  }
151}
152```
153
154## 使用AudioCapturer录制本端的通话声音
155
156  该过程与[使用AudioCapturer开发音频录制功能](using-audiocapturer-for-recording.md)过程相似,关键区别在于audioCapturerInfo参数和音频数据流向。audioCapturerInfo参数中音源类型需设置为语音通话:SOURCE_TYPE_VOICE_COMMUNICATION。
157
158  所有录制均需要申请麦克风权限:ohos.permission.MICROPHONE,申请方式请参考[向用户申请授权](../../security/AccessToken/request-user-authorization.md)。
159
160```ts
161import { audio } from '@kit.AudioKit';
162import { fileIo as fs } from '@kit.CoreFileKit';
163import { BusinessError } from '@kit.BasicServicesKit';
164import { common } from '@kit.AbilityKit';
165
166const TAG = 'VoiceCallDemoForAudioCapturer';
167class Options {
168  offset?: number;
169  length?: number;
170}
171
172// 与使用AudioCapturer开发音频录制功能过程相似,关键区别在于audioCapturerInfo参数和音频数据流向。
173let bufferSize: number = 0;
174let audioCapturer: audio.AudioCapturer | undefined = undefined;
175let audioStreamInfo: audio.AudioStreamInfo = {
176  samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // 采样率。
177  channels: audio.AudioChannel.CHANNEL_2, // 通道。
178  sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式。
179  encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式。
180};
181let audioCapturerInfo: audio.AudioCapturerInfo = {
182  // 需使用通话场景相应的参数。
183  source: audio.SourceType.SOURCE_TYPE_VOICE_COMMUNICATION, // 音源类型:语音通话。
184  capturerFlags: 0 // 音频采集器标志:默认为0即可。
185};
186let audioCapturerOptions: audio.AudioCapturerOptions = {
187  streamInfo: audioStreamInfo,
188  capturerInfo: audioCapturerInfo
189};
190// 请在组件内获取context,确保this.getUIContext().getHostContext()返回结果为UIAbilityContext。
191let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
192let path = context.cacheDir;
193let filePath = path + '/StarWars10s-2C-48000-4SW.wav';
194let file: fs.File = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
195let readDataCallback = (buffer: ArrayBuffer) => {
196  let options: Options = {
197    offset: bufferSize,
198    length: buffer.byteLength
199  };
200  fs.writeSync(file.fd, buffer, options);
201  bufferSize += buffer.byteLength;
202};
203
204// 初始化,创建实例,设置监听事件。
205async function init() {
206  audio.createAudioCapturer(audioCapturerOptions, (err: BusinessError, capturer: audio.AudioCapturer) => { // 创建AudioCapturer实例。
207    if (err) {
208      console.error(`Invoke createAudioCapturer failed, code is ${err.code}, message is ${err.message}`);
209      return;
210    }
211    console.info(`${TAG}: create AudioCapturer success`);
212    audioCapturer = capturer;
213    if (audioCapturer !== undefined) {
214      audioCapturer.on('markReach', 1000, (position: number) => { // 订阅markReach事件,当采集的帧数达到1000帧时触发回调。
215        if (position === 1000) {
216          console.info('ON Triggered successfully');
217        }
218      });
219      audioCapturer.on('periodReach', 2000, (position: number) => { // 订阅periodReach事件,当采集的帧数每达到2000时触发回调。
220        if (position === 2000) {
221          console.info('ON Triggered successfully');
222        }
223      });
224      audioCapturer.on('readData', readDataCallback);
225    }
226  });
227}
228
229// 开始一次音频采集。
230async function start() {
231  if (audioCapturer !== undefined) {
232    let stateGroup: number[] = [audio.AudioState.STATE_PREPARED, audio.AudioState.STATE_PAUSED, audio.AudioState.STATE_STOPPED];
233    if (stateGroup.indexOf(audioCapturer.state.valueOf()) === -1) { // 当且仅当状态为STATE_PREPARED、STATE_PAUSED和STATE_STOPPED之一时才能启动采集。
234      console.error(`${TAG}: start failed`);
235      return;
236    }
237    audioCapturer.start((err: BusinessError) => {
238      if (err) {
239        console.error('Capturer start failed.');
240      } else {
241        console.info('Capturer start success.');
242      }
243    });
244  }
245}
246
247// 停止采集。
248async function stop() {
249  if (audioCapturer !== undefined) {
250    // 只有采集器状态为STATE_RUNNING或STATE_PAUSED的时候才可以停止。
251    if (audioCapturer.state.valueOf() !== audio.AudioState.STATE_RUNNING && audioCapturer.state.valueOf() !== audio.AudioState.STATE_PAUSED) {
252      console.info('Capturer is not running or paused');
253      return;
254    }
255    await audioCapturer.stop(); // 停止采集。
256    if (audioCapturer.state.valueOf() === audio.AudioState.STATE_STOPPED) {
257      console.info('Capturer stopped');
258    } else {
259      console.error('Capturer stop failed');
260    }
261  }
262}
263
264// 销毁实例,释放资源。
265async function release() {
266  if (audioCapturer !== undefined) {
267    // 采集器状态不是STATE_RELEASED或STATE_NEW状态,才能release。
268    if (audioCapturer.state.valueOf() === audio.AudioState.STATE_RELEASED || audioCapturer.state.valueOf() === audio.AudioState.STATE_NEW) {
269      console.info('Capturer already released');
270      return;
271    }
272    await audioCapturer.release(); // 释放资源。
273    if (audioCapturer.state.valueOf() === audio.AudioState.STATE_RELEASED) {
274      console.info('Capturer released');
275    } else {
276      console.error('Capturer release failed');
277    }
278  }
279}
280```
281