• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2* Copyright (C) 2023-2025 Huawei Device Co., Ltd.
3* Licensed under the Apache License, Version 2.0 (the "License");
4* you may not use this file except in compliance with the License.
5* You may obtain a copy of the License at
6*
7* http://www.apache.org/licenses/LICENSE-2.0
8*
9* Unless required by applicable law or agreed to in writing, software
10* distributed under the License is distributed on an "AS IS" BASIS,
11* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12* See the License for the specific language governing permissions and
13* limitations under the License.
14*/
15import common from '@ohos.app.ability.common';
16import fs from '@ohos.file.fs';
17import audio from '@ohos.multimedia.audio';
18import router from '@ohos.router';
19import resourceManager from '@ohos.resourceManager';
20import { BusinessError } from '@ohos.base';
21
22class Options {
23  offset: number = 0;
24  length: number = 0;
25}
26
27@Entry
28@Component
29struct PreferOutputDevice {
30  private appContext?: common.Context;
31  private audioRendererOption: audio.AudioRendererOptions = {
32    streamInfo: {
33      samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000,
34      channels: audio.AudioChannel.CHANNEL_2,
35      sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
36      encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW
37    },
38    rendererInfo: {
39      content: audio.ContentType.CONTENT_TYPE_SPEECH,
40      usage: audio.StreamUsage.STREAM_USAGE_VOICE_COMMUNICATION,
41      rendererFlags: 0
42    }
43  };
44  private audioRenderer?: audio.AudioRenderer;
45  private audioSource = 'test1.wav';
46  private fileDescriptor?: resourceManager.RawFileDescriptor;
47  private audioRoutingManager?: audio.AudioRoutingManager;
48  @State preferOutputDeviceName: string = '';
49
50  async aboutToAppear(): Promise<void> {
51    this.init();
52  }
53
54  async init(): Promise<void> {
55    if (this.appContext) {
56      return;
57    }
58    this.appContext = getContext();
59    let audioManager = audio.getAudioManager();
60    this.audioRoutingManager = audioManager.getRoutingManager();
61    this.audioRoutingManager.on('deviceChange', audio.DeviceFlag.OUTPUT_DEVICES_FLAG, async (deviceChanged) => {
62      await this.getPreferOutputDeviceForRendererInfo();
63    });
64    await this.getPreferOutputDeviceForRendererInfo();
65    await this.getStageFileDescriptor(this.audioSource).then((res) => {
66      this.fileDescriptor = res;
67    });
68    await this.renderPlay();
69  }
70
71  async over(): Promise<void> {
72    this.appContext = undefined;
73    if (this.audioRenderer) {
74      await this.audioRenderer.release();
75      this.audioRenderer = undefined;
76    }
77    if (this.fileDescriptor) {
78      this.closeResource(this.audioSource);
79      this.fileDescriptor = undefined;
80    }
81    this.audioRoutingManager = undefined;
82  }
83
84  onBackPress(): void {
85    this.over();
86  }
87
88  async onPageHide(): Promise<void> {
89    this.over();
90  }
91
92  onPageShow(): void {
93    this.init();
94  }
95
96  async getStageFileDescriptor(fileName: string): Promise<resourceManager.RawFileDescriptor | undefined> {
97    let fileDescriptor: resourceManager.RawFileDescriptor | undefined = undefined;
98    if (this.appContext) {
99      let mgr = this.appContext.resourceManager;
100      await mgr.getRawFd(fileName).then(value => {
101        fileDescriptor = value;
102        console.log('case getRawFileDescriptor success fileName: ' + fileName);
103      }).catch((error: BusinessError) => {
104        console.log('case getRawFileDescriptor err: ' + error);
105      });
106    }
107    return fileDescriptor;
108  }
109
110  async closeResource(fileName: string): Promise<void> {
111    if (this.appContext) {
112      let mgr = this.appContext.resourceManager;
113      await mgr.closeRawFd(fileName).then(() => {
114        console.log('case closeRawFd success fileName: ' + fileName);
115      }).catch((error: BusinessError) => {
116        console.log('case closeRawFd err: ' + error);
117      });
118    }
119  }
120
121  getPreferOutputDeviceForRendererInfo(): void {
122    if (this.audioRoutingManager) {
123      this.audioRoutingManager.getPreferOutputDeviceForRendererInfo(this.audioRendererOption.rendererInfo)
124        .then(data => {
125          console.log(`--zhangkai--getPreferOutputDeviceForRendererInfo data:${JSON.stringify(data[0])}`);
126          this.preferOutputDeviceName = this.getDeviceList(data[0]);
127        }).catch((err: BusinessError) => {
128        this.preferOutputDeviceName = 'Invalid';
129        console.log(`getPreferOutputDeviceForRendererInfo err:${JSON.stringify(err)}`);
130      });
131    }
132  }
133
134  getZNDeviceTypeName(deviceTypeName: string): string {
135    if (this.appContext) {
136      let map = new Map<string, string>();
137      map.set('EARPIECE', this.appContext.resourceManager.getStringSync($r('app.string.EarPiece').id));
138      map.set('SPEAKER', this.appContext.resourceManager.getStringSync($r('app.string.Speaker').id));
139      map.set('WIRED_HEADSET', this.appContext.resourceManager.getStringSync($r('app.string.WiredHeadset').id));
140      map.set('WIRED_HEADPHONES', this.appContext.resourceManager.getStringSync($r('app.string.WiredHeadPhones').id));
141      map.set('BLUETOOTH_A2DP', this.appContext.resourceManager.getStringSync($r('app.string.Bluetooth_A2DP').id));
142      map.set('BLUETOOTH_SCO', this.appContext.resourceManager.getStringSync($r('app.string.BLUETOOTH_SCO').id));
143      map.set('USB_HEADSET', this.appContext.resourceManager.getStringSync($r('app.string.USB_Headset').id));
144      if (map.get(deviceTypeName)) {
145        return map.get(deviceTypeName) as string;
146      } else {
147        return 'Invalid';
148      }
149    } else {
150      return '';
151    }
152  }
153
154  getDeviceList(deviceDescriptor: audio.AudioDeviceDescriptor): string {
155    let deviceTypeName = this.getDeviceTypeNameByValue(deviceDescriptor.deviceType);
156    return this.getZNDeviceTypeName(deviceTypeName);
157  }
158
159  getDeviceTypeNameByValue(value: audio.DeviceType): string {
160    let map = new Map<number, string>();
161    map.set(1, 'EARPIECE');
162    map.set(2, 'SPEAKER');
163    map.set(3, 'WIRED_HEADSET');
164    map.set(4, 'WIRED_HEADPHONES');
165    map.set(8, 'BLUETOOTH_A2DP');
166    map.set(7, 'BLUETOOTH_SCO');
167    map.set(22, 'USB_HEADSET');
168    if (map.get(value)) {
169      return map.get(value) as string;
170    }
171    return 'Invalid';
172  }
173
174  async renderPlay(): Promise<void> {
175    if (this.audioRenderer) {
176      return;
177    }
178    try {
179      this.audioRenderer = await audio.createAudioRenderer(this.audioRendererOption);
180    } catch (err) {
181      let error = err as BusinessError;
182      console.error(`audioRenderer create : Error: ${JSON.stringify(error)}`);
183      return;
184    }
185    let bufferSize : number;
186    try {
187      bufferSize = await this.audioRenderer.getBufferSize();
188      await this.audioRenderer.start();
189    } catch (err) {
190      let error = err as BusinessError;
191      console.error(`audioRenderer start : Error: ${JSON.stringify(error)}`);
192      return;
193    }
194
195    try {
196      if (!this.fileDescriptor) {
197        return;
198      }
199      let startOffset = this.fileDescriptor.offset;
200      let cur = startOffset;
201      let buf = new ArrayBuffer(bufferSize);
202      while (true) {
203        // when render released,state is changed to STATE_RELEASED
204        if (this.audioRenderer.state === audio.AudioState.STATE_RELEASED) {
205          break;
206        }
207        while (cur <= startOffset + this.fileDescriptor.length) {
208          // when render released,state is changed to STATE_RELEASED
209          if (this.audioRenderer.state.valueOf() === audio.AudioState.STATE_RELEASED.valueOf()) {
210            break;
211          }
212          let options: Options = {
213            offset: cur,
214            length: bufferSize
215          }
216          await fs.read(this.fileDescriptor.fd, buf, options);
217          await this.audioRenderer.write(buf);
218          cur += bufferSize;
219        }
220        cur = startOffset;
221      }
222    } catch (err) {
223      let error = err as BusinessError;
224      console.error(`audioRenderer write : Error: ${JSON.stringify(error)}`);
225    }
226  }
227
228  build() {
229    Column() {
230      Row() {
231        Navigation() {
232          NavRouter() {
233            NavDestination() {
234            }
235          }
236        }
237        .height('100%')
238        .width('100%')
239        .hideBackButton(false)
240        .titleMode(NavigationTitleMode.Mini)
241        .title($r('app.string.SelectOutputDevice'))
242        .mode(NavigationMode.Stack);
243      }.height(56).width('100%').id('back_btn')
244      .onClick(async () => {
245        await router.pushUrl({ url: 'pages/Index' });
246      });
247
248      Column() {
249        Row() {
250          Row() {
251            Image($r('app.media.ic_call')).width(48).height(48);
252            Text($r('app.string.VoiceCallType'))
253              .fontSize(16)
254              .margin({ left: 12 })
255              .fontFamily($r('sys.string.ohos_id_text_font_family_medium'))
256              .fontColor('#182431')
257              .fontWeight(500);
258          }
259
260          Row() {
261            Text(this.preferOutputDeviceName)
262              .id('device_name_text')
263              .fontFamily($r('sys.string.ohos_id_text_font_family_medium'))
264              .fontColor('#182431')
265              .fontWeight(400)
266              .opacity(0.6);
267          }
268        }
269        .justifyContent(FlexAlign.SpaceBetween)
270        .height(72)
271        .width('100%')
272        .padding({ left: '3.35%', right: '3.35%' })
273        .backgroundColor(Color.White)
274        .borderRadius(24);
275      }.padding({ left: '3.35%', right: '3.35%' });
276    }
277    .height('100%').width('100%').backgroundColor('#f1f3f5');
278  }
279}