• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1
2/*
3* Copyright (C) 2024 Huawei Device Co., Ltd.
4* Licensed under the Apache License, Version 2.0 (the "License");
5* you may not use this file except in compliance with the License.
6* You may obtain a copy of the License at
7*
8* http://www.apache.org/licenses/LICENSE-2.0
9*
10* Unless required by applicable law or agreed to in writing, software
11* distributed under the License is distributed on an "AS IS" BASIS,
12* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13* See the License for the specific language governing permissions and
14* limitations under the License.
15*/
16
17import { audio } from '@kit.AudioKit';
18import { fileIo } from '@kit.CoreFileKit';
19import { BusinessError } from '@kit.BasicServicesKit';
20import { resourceManager } from '@kit.LocalizationKit';
21import { common } from '@kit.AbilityKit';
22import { avSession, AVCastPicker, AVCastPickerState } from '@kit.AVSessionKit';
23
24class Options {
25  public offset: number = 0;
26  public length: number = 0;
27}
28
29@Entry
30@Component
31struct customPicker {
32  @State session: avSession.AVSession | undefined = undefined;
33  @State avCastPickerColor:Color = Color.White;
34  @State pickerImage: ResourceStr = $r('app.media.ic_earpiece');
35  private appContext?: common.Context | undefined = undefined;
36  private audioManager: audio.AudioManager | undefined = undefined;
37  private audioRoutingManager: audio.AudioRoutingManager | undefined = undefined;
38  private audioRenderer: audio.AudioRenderer | undefined = undefined;
39  private audioSource = 'test1.wav';
40  private fileDescriptor?: resourceManager.RawFileDescriptor | undefined = undefined;
41  private audioRendererInfo: audio.AudioRendererInfo = {
42    usage: audio.StreamUsage.STREAM_USAGE_VOICE_COMMUNICATION,
43    rendererFlags: 0,
44  };
45  private audioStreamInfo: audio.AudioStreamInfo = {
46    samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // 采样率
47    channels: audio.AudioChannel.CHANNEL_2, // 通道
48    sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式
49    encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式
50  };
51  private  audioRendererOption: audio.AudioRendererOptions = {
52    streamInfo: this.audioStreamInfo,
53    rendererInfo: this.audioRendererInfo
54  };
55
56  async aboutToAppear() {
57    console.log('about to appear');
58    await this.init();
59  }
60
61  async init() {
62    if (!this.appContext) {
63      this.appContext = getContext();
64    }
65    this.session = await avSession.createAVSession(this.appContext, 'voiptest', 'voice_call');
66    this.observerDevices();
67  }
68
69  async observerDevices() {
70    this.audioManager = audio.getAudioManager();
71    if (!this.audioManager) {
72      console.error('get audioManager failed');
73      return;
74    }
75    this.audioRoutingManager = this.audioManager.getRoutingManager();
76    if(!this.audioRoutingManager) {
77      return;
78    }
79    let desc: audio.AudioDeviceDescriptors =
80      this.audioRoutingManager.getPreferredOutputDeviceForRendererInfoSync(this.audioRendererInfo);
81    this.changePickerShow(desc); //第一次拉起picker时获取当前输出设备
82    this.audioRoutingManager.on('preferOutputDeviceChangeForRendererInfo', this.audioRendererInfo, (desc: audio.AudioDeviceDescriptors) => {
83      if(!this.audioRoutingManager) {
84        return;
85      }
86      console.log(`device change to: ${desc[0].deviceType}`);
87      let devices: audio.AudioDeviceDescriptors =
88        this.audioRoutingManager.getPreferredOutputDeviceForRendererInfoSync(this.audioRendererInfo);
89      this.changePickerShow(devices);
90    });
91  }
92
93  private changePickerShow(desc: audio.AudioDeviceDescriptors): void {
94    if (desc[0].deviceType === 2) {
95      this.pickerImage = $r('app.media.ic_public_sound');
96    } else if (desc[0].deviceType === 7) {
97      this.pickerImage = $r('app.media.ic_bluetooth');
98    } else {
99      this.pickerImage = $r('app.media.ic_earpiece');
100    }
101  }
102
103  async getStageFileDescriptor(fileName: string): Promise<resourceManager.RawFileDescriptor | undefined> {
104    let fileDescriptor: resourceManager.RawFileDescriptor | undefined = undefined;
105    if (this.appContext) {
106      let mgr = this.appContext.resourceManager;
107      this.fileDescriptor = mgr.getRawFdSync(fileName);
108      await mgr.getRawFd(fileName).then(value => {
109        fileDescriptor = value;
110        console.log(`case getRawFileDescriptor success fileName: ${fileName}`);
111      }).catch((err: BusinessError) => {
112        console.error(`case getRawFileDescriptor err: code: ${err.code}, message: ${err.message}`);
113      });
114    }
115    return fileDescriptor;
116  }
117
118  async startRenderer(): Promise<void> {
119    if (this.audioRenderer !== undefined) {
120      return;
121    }
122    this.getStageFileDescriptor(this.audioSource).then((res) => {
123      this.fileDescriptor = res;
124    });
125    if (!this.fileDescriptor) {
126      return;
127    }
128    let file: resourceManager.RawFileDescriptor = this.fileDescriptor;
129    try {
130      this.audioRenderer = await audio.createAudioRenderer(this.audioRendererOption);
131    } catch (error) {
132      console.error(`audioRenderer create : Error: ${JSON.stringify(error)}`);
133      return;
134    }
135    let bufferSize: number = this.fileDescriptor.offset;
136    let writeDataCallback = (buffer: ArrayBuffer) => {
137      let options: Options = {
138        offset: bufferSize,
139        length: buffer.byteLength
140      }
141      fileIo.readSync(file.fd, buffer, options);
142      bufferSize += buffer.byteLength;
143    };
144    this.audioRenderer.on('writeData', writeDataCallback);
145    await this.audioRenderer.start();
146  }
147
148  async stopRenderer(): Promise<void> {
149    if (this.audioRenderer) {
150      await this.audioRenderer.release();
151      this.audioRenderer = undefined;
152    }
153    if (this.fileDescriptor) {
154      this.closeResource(this.audioSource);
155      this.fileDescriptor = undefined;
156    }
157  }
158
159  async closeResource(fileName: string): Promise<void> {
160    if (this.appContext) {
161      let mgr = this.appContext.resourceManager;
162      await mgr.closeRawFd(fileName).then(() => {
163        console.log(`case closeRawFd success fileName: ${fileName}`);
164      }).catch((err: BusinessError) => {
165        console.error(`case closeRawFd err: code: ${err.code}, message: ${err.message}`);
166      });
167    }
168  }
169
170  onBackPress(): void {
171    this.stopRenderer();
172    this.destroy();
173  }
174
175  async onPageHide(): Promise<void> {
176    this.stopRenderer();
177    this.destroy();
178  }
179
180  onPageShow(): void {
181    this.init();
182  }
183
184  destroy(): void {
185    this.appContext = undefined;
186    if (this.audioRoutingManager !== undefined) {
187      this.audioRoutingManager.off('preferOutputDeviceChangeForRendererInfo');
188    }
189    try {
190      if (this.session) {
191        this.session?.destroy();
192      }
193    } catch (err) {
194      console.error('session destroy failed');
195    }
196  }
197
198  aboutToDisappear() {
199    console.log('about to disappear');
200    this.destroy();
201  }
202
203  @Builder
204  ImageBuilder(): void {
205    Image(this.pickerImage)
206      .size({ width: '100%', height: '100%' })
207      .backgroundColor('#00000000')
208      .fillColor(Color.Black)
209  }
210
211  build() {
212    Row() {
213      Column() {
214        Button() {
215          Text('start').fontSize(22).fontColor(Color.White)
216        }
217        .size({ width: 64, height: 64 })
218        .onClick(() => {
219          this.startRenderer();
220        })
221        .type(ButtonType.Circle)
222      }
223      .size({ width: '33%', height: 64 })
224
225      Column() {
226        Button() {
227          Text('stop').fontSize(22).fontColor(Color.White)
228        }
229        .size({ width: 64, height: 64 })
230        .onClick(() => {
231          this.stopRenderer();
232        })
233        .type(ButtonType.Circle)
234      }
235      .size({ width: '33%', height: 64 })
236
237      Column() {
238        Button() {
239          AVCastPicker({
240            normalColor: this.avCastPickerColor,
241            activeColor: this.avCastPickerColor,
242            customPicker: (): void => this.ImageBuilder(),
243            onStateChange: (state: AVCastPickerState) => {
244              console.info(`change avcastpicker: ${state}`);
245            }
246          })
247            .size({ width: 45, height: 45 })
248        }
249        .size({ width: 64, height: 64 })
250        .type(ButtonType.Circle)
251        .backgroundColor(Color.Orange)
252      }
253      .size({ width: '33%', height: 64 })
254    }
255    .margin({ top: 300})
256    .justifyContent(FlexAlign.SpaceBetween)
257  }
258}