FreeRDP
Loading...
Searching...
No Matches
dsp_mediacodec.c
1
20#include <freerdp/config.h>
21
22#include "dsp_mediacodec.h"
23
24#include <winpr/assert.h>
25#include <winpr/cast.h>
26#include <freerdp/log.h>
27
28#include <media/NdkMediaCodec.h>
29#include <media/NdkMediaFormat.h>
30
31#define TAG FREERDP_TAG("dsp.mediacodec")
32
33static const char* AAC_MIME = "audio/mp4a-latm";
34static const int AAC_OBJECT_LC = 2; /* AAC-LC */
35
36/* AMEDIAFORMAT_KEY_CSD_0 has no NDK constant below API 28; the string works at runtime */
37static const char* MEDIACODEC_KEY_CSD_0 = "csd-0";
38
39static const int64_t MEDIACODEC_DSP_DEQUEUE_TIMEOUT = 10000; /* microseconds */
40static const int MEDIACODEC_DSP_MAX_RETRIES = 100;
41static const size_t AAC_FRAME_SAMPLES = 1024;
42static const int32_t DECODER_MAX_INPUT_SIZE = 65536;
43static const UINT32 AAC_MAX_SAMPLE_RATE = 96000;
44
45typedef struct
46{
47 AMediaCodec* codec;
48 BOOL started;
49 BOOL isEncoder;
50 UINT32 sampleRate;
51 UINT16 channels;
52 uint64_t sampleCount;
53
54 /* Encoder PCM accumulation buffer */
55 BYTE* encBuffer;
56 size_t encBufferedBytes;
57 size_t encFrameBytes; /* 1024 * channels * sizeof(int16_t) */
58
59 /* Decoder detected output format */
60 UINT32 outSampleRate;
61 UINT16 outChannels;
62 BOOL formatMismatchLogged;
63 BOOL srcMismatchLogged;
64} MEDIACODEC_AAC;
65
66#define mediacodec_aac_warn(status, what) \
67 mediacodec_aac_warn_((status), (what), __FILE__, __func__, __LINE__)
68
69static void mediacodec_aac_warn_(media_status_t status, const char* what, const char* file,
70 const char* fkt, size_t line)
71{
72 if (status == AMEDIA_OK)
73 return;
74
75 wLog* log = WLog_Get(TAG);
76 if (WLog_IsLevelActive(log, WLOG_WARN))
77 WLog_PrintTextMessage(log, WLOG_WARN, line, file, fkt, "%s failed: %d", what, status);
78}
79
80/* MPEG-4 sampling frequency index table */
81WINPR_ATTR_NODISCARD
82static int aac_sample_rate_index(UINT32 rate)
83{
84 static const UINT32 rates[] = { 96000, 88200, 64000, 48000, 44100, 32000, 24000,
85 22050, 16000, 12000, 11025, 8000, 7350 };
86 for (size_t i = 0; i < ARRAYSIZE(rates); i++)
87 {
88 if (rates[i] == rate)
89 return WINPR_ASSERTING_INT_CAST(int, i);
90 }
91 return -1;
92}
93
94WINPR_ATTR_NODISCARD
95static BOOL aac_build_asc(UINT32 rate, UINT16 channels, BYTE asc[2])
96{
97 WINPR_ASSERT(asc);
98
99 const int sfIndex = aac_sample_rate_index(rate);
100 if (sfIndex < 0)
101 return FALSE;
102
103 if ((channels < 1) || (channels > 8))
104 return FALSE;
105
106 /* channelConfiguration 8 is reserved, 7.1 is signalled as 7 */
107 const int channelConfig = (channels == 8) ? 7 : channels;
108
109 asc[0] = WINPR_ASSERTING_INT_CAST(BYTE, (AAC_OBJECT_LC << 3) | ((sfIndex >> 1) & 0x07));
110 asc[1] =
111 WINPR_ASSERTING_INT_CAST(BYTE, ((sfIndex & 0x01) << 7) | ((channelConfig & 0x0F) << 3));
112 return TRUE;
113}
114
115/* Reject extra format bytes that are not an AudioSpecificConfig. */
116WINPR_ATTR_NODISCARD
117static BOOL aac_asc_plausible(const BYTE* data, size_t size)
118{
119 WINPR_ASSERT(data);
120
121 if (size < 2)
122 return FALSE;
123
124 const int objectType = (data[0] >> 3) & 0x1F;
125 const int sfIndex = ((data[0] & 0x07) << 1) | ((data[1] >> 7) & 0x01);
126
127 switch (objectType)
128 {
129 case 1: /* AAC Main */
130 case 2: /* AAC-LC */
131 case 3: /* AAC-SSR */
132 case 4: /* AAC-LTP */
133 case 5: /* SBR */
134 case 29: /* PS */
135 break;
136 default:
137 return FALSE;
138 }
139
140 /* Explicit SBR/PS signalling appends an extension sampling frequency index plus the base
141 * object type and config, so a two byte buffer claiming either is truncated. */
142 if (((objectType == 5) || (objectType == 29)) && (size < 4))
143 return FALSE;
144
145 if (sfIndex == 15)
146 return size >= 5; /* explicit 24 bit sampling frequency follows the index */
147
148 if (sfIndex > 12)
149 return FALSE;
150
151 /* channelConfiguration 8..15 is reserved; 0 defers the layout to the specific config */
152 return (((data[1] >> 3) & 0x0F) <= 7);
153}
154
155/* The format extra bytes may hold a bare AudioSpecificConfig or a HEAACWAVEINFO header
156 * (wPayloadType, wAudioProfileLevelIndication, wStructType, wReserved1, dwReserved2) in front
157 * of one. Anything else is not a config and must not reach the decoder. */
158WINPR_ATTR_NODISCARD
159static BOOL aac_format_asc(const AUDIO_FORMAT* format, const BYTE** pasc, size_t* psize)
160{
161 WINPR_ASSERT(format);
162 WINPR_ASSERT(pasc);
163 WINPR_ASSERT(psize);
164
165 const size_t heaacInfoSize = 12;
166
167 if (!format->data || (format->cbSize < 2))
168 return FALSE;
169
170 if (aac_asc_plausible(format->data, format->cbSize))
171 {
172 *pasc = format->data;
173 *psize = format->cbSize;
174 return TRUE;
175 }
176
177 if ((format->cbSize >= heaacInfoSize + 2) &&
178 aac_asc_plausible(&format->data[heaacInfoSize], format->cbSize - heaacInfoSize))
179 {
180 *pasc = &format->data[heaacInfoSize];
181 *psize = format->cbSize - heaacInfoSize;
182 return TRUE;
183 }
184
185 return FALSE;
186}
187
188WINPR_ATTR_NODISCARD
189static BOOL mediacodec_drain_output(MEDIACODEC_AAC* mc, wStream* out)
190{
191 WINPR_ASSERT(mc);
192 WINPR_ASSERT(out);
193
194 /* Wait for the codec on the first attempt only: once it has produced output the remaining
195 * buffers are already queued, and paying the timeout again just stalls the channel. */
196 int64_t timeout = MEDIACODEC_DSP_DEQUEUE_TIMEOUT;
197
198 for (int loops = 0; loops <= MEDIACODEC_DSP_MAX_RETRIES; loops++)
199 {
200 AMediaCodecBufferInfo info = { 0 };
201 const ssize_t outIdx = AMediaCodec_dequeueOutputBuffer(mc->codec, &info, timeout);
202 if (outIdx >= 0)
203 {
204 timeout = 0;
205
206 if (info.size > 0 && (info.flags & AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG) == 0)
207 {
208 size_t outCap = 0;
209 uint8_t* outBuf = AMediaCodec_getOutputBuffer(mc->codec, (size_t)outIdx, &outCap);
210
211 /* offset and size come from the driver, they are not trusted bounds */
212 if (outBuf && ((info.offset < 0) || ((size_t)info.offset > outCap) ||
213 ((size_t)info.size > outCap - (size_t)info.offset)))
214 {
215 WLog_ERR(TAG, "output buffer info out of range: offset %d size %d cap %zu",
216 info.offset, info.size, outCap);
217 mediacodec_aac_warn(
218 AMediaCodec_releaseOutputBuffer(mc->codec, (size_t)outIdx, FALSE),
219 "releaseOutputBuffer");
220 return FALSE;
221 }
222
223 if (outBuf)
224 {
225 if (!Stream_EnsureRemainingCapacity(out, (size_t)info.size))
226 {
227 mediacodec_aac_warn(
228 AMediaCodec_releaseOutputBuffer(mc->codec, (size_t)outIdx, FALSE),
229 "releaseOutputBuffer");
230 return FALSE;
231 }
232 Stream_Write(out, outBuf + info.offset, (size_t)info.size);
233 }
234 }
235 const media_status_t rc =
236 AMediaCodec_releaseOutputBuffer(mc->codec, (size_t)outIdx, FALSE);
237 if (rc != AMEDIA_OK)
238 {
239 WLog_ERR(TAG, "releaseOutputBuffer failed: %d", rc);
240 return FALSE;
241 }
242 }
243 else if (outIdx == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED)
244 {
245 AMediaFormat* newFormat = AMediaCodec_getOutputFormat(mc->codec);
246 if (newFormat)
247 {
248 int32_t newRate = 0;
249 int32_t newCh = 0;
250 if (AMediaFormat_getInt32(newFormat, AMEDIAFORMAT_KEY_SAMPLE_RATE, &newRate) &&
251 (newRate > 0))
252 mc->outSampleRate = (UINT32)newRate;
253 if (AMediaFormat_getInt32(newFormat, AMEDIAFORMAT_KEY_CHANNEL_COUNT, &newCh) &&
254 (newCh > 0) && (newCh <= UINT16_MAX))
255 mc->outChannels = WINPR_ASSERTING_INT_CAST(UINT16, newCh);
256 mediacodec_aac_warn(AMediaFormat_delete(newFormat), "AMediaFormat_delete");
257
258 WLog_DBG(TAG, "output format %" PRIu32 " Hz, %" PRIu16 " ch", mc->outSampleRate,
259 mc->outChannels);
260
261 /* The caller opened its device for the negotiated format and there is no
262 * resampler here, so a codec decoding to something else (SBR doubles the rate)
263 * plays back detuned. Report it instead of failing the channel. */
264 if (!mc->formatMismatchLogged &&
265 ((mc->outSampleRate != mc->sampleRate) || (mc->outChannels != mc->channels)))
266 {
267 mc->formatMismatchLogged = TRUE;
268 WLog_WARN(TAG,
269 "decoder output %" PRIu32 " Hz/%" PRIu16 " ch differs from the "
270 "negotiated %" PRIu32 " Hz/%" PRIu16 " ch, playback will be wrong",
271 mc->outSampleRate, mc->outChannels, mc->sampleRate, mc->channels);
272 }
273 }
274 }
275 else if (outIdx == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED)
276 {
277 continue;
278 }
279 else if (outIdx == AMEDIACODEC_INFO_TRY_AGAIN_LATER)
280 {
281 break;
282 }
283 else
284 {
285 WLog_ERR(TAG, "dequeueOutputBuffer returned %zd", outIdx);
286 return FALSE;
287 }
288 }
289 return TRUE;
290}
291
292WINPR_ATTR_NODISCARD
293static BOOL mediacodec_feed_buffer(MEDIACODEC_AAC* mc, const BYTE* data, size_t length,
294 size_t samplesQueued, wStream* out)
295{
296 WINPR_ASSERT(mc);
297 WINPR_ASSERT(data);
298 WINPR_ASSERT(out);
299
300 int retries = 0;
301 ssize_t inIdx = -1;
302 while ((inIdx = AMediaCodec_dequeueInputBuffer(mc->codec, MEDIACODEC_DSP_DEQUEUE_TIMEOUT)) < 0)
303 {
304 if (inIdx != AMEDIACODEC_INFO_TRY_AGAIN_LATER)
305 {
306 WLog_ERR(TAG, "dequeueInputBuffer failed [%zd]", inIdx);
307 return FALSE;
308 }
309
310 if (++retries > MEDIACODEC_DSP_MAX_RETRIES)
311 {
312 WLog_ERR(TAG, "dequeueInputBuffer timeout [%zd]", inIdx);
313 return FALSE;
314 }
315 }
316
317 size_t inCap = 0;
318 uint8_t* inBuf = AMediaCodec_getInputBuffer(mc->codec, (size_t)inIdx, &inCap);
319 if (!inBuf || inCap < length)
320 {
321 WLog_ERR(TAG, "input buffer too small: inCap %zu < length %zu", inCap, length);
322 mediacodec_aac_warn(AMediaCodec_queueInputBuffer(mc->codec, (size_t)inIdx, 0, 0, 0, 0),
323 "queueInputBuffer");
324 return FALSE;
325 }
326
327 memcpy(inBuf, data, length);
328
329 /* Monotonically increasing PTS in microseconds */
330 uint64_t ptsUs = 0;
331 if (mc->sampleRate > 0)
332 ptsUs = (mc->sampleCount / mc->sampleRate) * 1000000ULL +
333 (mc->sampleCount % mc->sampleRate) * 1000000ULL / mc->sampleRate;
334 mc->sampleCount += samplesQueued;
335
336 if (AMediaCodec_queueInputBuffer(mc->codec, (size_t)inIdx, 0, length, ptsUs, 0) != AMEDIA_OK)
337 {
338 WLog_ERR(TAG, "queueInputBuffer failed");
339 return FALSE;
340 }
341
342 return mediacodec_drain_output(mc, out);
343}
344
345BOOL mediacodec_aac_dsp_init(FREERDP_DSP_COMMON_CONTEXT* context, size_t frames_per_packet)
346{
347 WINPR_ASSERT(context);
348 WINPR_UNUSED(frames_per_packet);
349
350 MEDIACODEC_AAC* mc = (MEDIACODEC_AAC*)calloc(1, sizeof(MEDIACODEC_AAC));
351 if (!mc)
352 return FALSE;
353 context->mediacodecAacInstance = mc;
354
355 if ((context->format.nSamplesPerSec == 0) ||
356 (context->format.nSamplesPerSec > AAC_MAX_SAMPLE_RATE) || (context->format.nChannels < 1) ||
357 (context->format.nChannels > 8))
358 {
359 WLog_ERR(TAG, "unusable AAC format %" PRIu32 " Hz, %" PRIu16 " ch",
360 context->format.nSamplesPerSec, context->format.nChannels);
361 free(mc);
362 context->mediacodecAacInstance = nullptr;
363 return FALSE;
364 }
365
366 mc->isEncoder = context->encoder;
367 mc->sampleRate = context->format.nSamplesPerSec;
368 mc->channels = context->format.nChannels;
369 mc->outSampleRate = mc->sampleRate;
370 mc->outChannels = mc->channels;
371 mc->sampleCount = 0;
372
373 if (mc->isEncoder)
374 {
375 mc->encFrameBytes = AAC_FRAME_SAMPLES * mc->channels * sizeof(int16_t);
376 mc->encBuffer = (BYTE*)calloc(mc->encFrameBytes, sizeof(BYTE));
377 if (!mc->encBuffer)
378 {
379 free(mc);
380 context->mediacodecAacInstance = nullptr;
381 return FALSE;
382 }
383 mc->encBufferedBytes = 0;
384 }
385
386 mc->codec = context->encoder ? AMediaCodec_createEncoderByType(AAC_MIME)
387 : AMediaCodec_createDecoderByType(AAC_MIME);
388 if (!mc->codec)
389 {
390 WLog_ERR(TAG, "AMediaCodec_create%scoderByType failed", context->encoder ? "En" : "De");
391 free(mc->encBuffer);
392 free(mc);
393 context->mediacodecAacInstance = nullptr;
394 return FALSE;
395 }
396
397 AMediaFormat* format = AMediaFormat_new();
398 if (!format)
399 {
400 mediacodec_aac_dsp_uninit(context);
401 return FALSE;
402 }
403
404 AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, AAC_MIME);
405 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE,
406 WINPR_ASSERTING_INT_CAST(int32_t, mc->sampleRate));
407 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_CHANNEL_COUNT,
408 WINPR_ASSERTING_INT_CAST(int32_t, mc->channels));
409
410 if (context->encoder)
411 {
412 const UINT64 bps = (UINT64)context->format.nAvgBytesPerSec * 8;
413 int32_t bitrate = 128000;
414 if (bps > INT32_MAX)
415 bitrate = INT32_MAX;
416 else if (bps > 0)
417 bitrate = WINPR_ASSERTING_INT_CAST(int32_t, bps);
418 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, bitrate);
419 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_AAC_PROFILE, AAC_OBJECT_LC);
420 }
421 else
422 {
423 /* Allow large compressed inputs to prevent buffer truncation */
424 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_MAX_INPUT_SIZE, DECODER_MAX_INPUT_SIZE);
425
426 const BYTE* asc = nullptr;
427 size_t ascSize = 0;
428 BYTE built[2];
429
430 if (!aac_format_asc(&context->format, &asc, &ascSize))
431 {
432 if (!aac_build_asc(mc->sampleRate, mc->channels, built))
433 {
434 WLog_ERR(TAG, "unsupported AAC sample rate %" PRIu32, mc->sampleRate);
435 mediacodec_aac_warn(AMediaFormat_delete(format), "AMediaFormat_delete");
436 mediacodec_aac_dsp_uninit(context);
437 return FALSE;
438 }
439
440 asc = built;
441 ascSize = sizeof(built);
442 }
443
444 AMediaFormat_setBuffer(format, MEDIACODEC_KEY_CSD_0, asc, ascSize);
445 }
446
447 media_status_t status =
448 AMediaCodec_configure(mc->codec, format, nullptr, nullptr,
449 context->encoder ? AMEDIACODEC_CONFIGURE_FLAG_ENCODE : 0);
450 mediacodec_aac_warn(AMediaFormat_delete(format), "AMediaFormat_delete");
451 if (status != AMEDIA_OK)
452 {
453 WLog_ERR(TAG, "AMediaCodec_configure failed: %d", status);
454 mediacodec_aac_dsp_uninit(context);
455 return FALSE;
456 }
457
458 status = AMediaCodec_start(mc->codec);
459 if (status != AMEDIA_OK)
460 {
461 WLog_ERR(TAG, "AMediaCodec_start failed: %d", status);
462 mediacodec_aac_dsp_uninit(context);
463 return FALSE;
464 }
465
466 mc->started = TRUE;
467 WLog_DBG(TAG, "MediaCodec AAC %scoder started: %" PRIu32 " Hz, %" PRIu16 " ch",
468 context->encoder ? "en" : "de", mc->sampleRate, mc->channels);
469 return TRUE;
470}
471
472void mediacodec_aac_dsp_uninit(FREERDP_DSP_COMMON_CONTEXT* context)
473{
474 if (!context)
475 return;
476
477 MEDIACODEC_AAC* mc = (MEDIACODEC_AAC*)context->mediacodecAacInstance;
478 if (!mc)
479 return;
480
481 if (mc->codec)
482 {
483 if (mc->started)
484 mediacodec_aac_warn(AMediaCodec_stop(mc->codec), "AMediaCodec_stop");
485 mediacodec_aac_warn(AMediaCodec_delete(mc->codec), "AMediaCodec_delete");
486 }
487 free(mc->encBuffer);
488 free(mc);
489 context->mediacodecAacInstance = nullptr;
490}
491
492BOOL mediacodec_aac_dsp_encode(FREERDP_DSP_COMMON_CONTEXT* context, const AUDIO_FORMAT* srcFormat,
493 const BYTE* data, size_t length, wStream* out)
494{
495 WINPR_ASSERT(context);
496 WINPR_ASSERT(srcFormat);
497
498 if (srcFormat->wFormatTag != WAVE_FORMAT_PCM)
499 {
500 WLog_WARN(TAG, "expected PCM input, got 0x%04x", (unsigned)srcFormat->wFormatTag);
501 return FALSE;
502 }
503
504 MEDIACODEC_AAC* mc = (MEDIACODEC_AAC*)context->mediacodecAacInstance;
505 if (!mc || !mc->codec || !mc->started || !data || length == 0 || !out)
506 return FALSE;
507
508 if (srcFormat->wBitsPerSample != 16)
509 {
510 WLog_ERR(TAG, "expected 16 bit PCM, got %" PRIu16 " bits", srcFormat->wBitsPerSample);
511 return FALSE;
512 }
513
514 const size_t srcFrameBytes = srcFormat->nChannels * sizeof(int16_t);
515 if ((srcFrameBytes == 0) || ((length % srcFrameBytes) != 0))
516 {
517 WLog_ERR(TAG, "PCM length %" PRIuz " is not a multiple of the %" PRIuz " byte frame",
518 length, srcFrameBytes);
519 return FALSE;
520 }
521
522 /* There is no resampler or channel mixer here and the dispatcher routes AAC straight to
523 * this backend, so a capture format that drifted from the negotiated one is only
524 * detectable at this point. */
525 if (!mc->srcMismatchLogged &&
526 ((srcFormat->nSamplesPerSec != mc->sampleRate) || (srcFormat->nChannels != mc->channels)))
527 {
528 mc->srcMismatchLogged = TRUE;
529 WLog_WARN(TAG,
530 "PCM source %" PRIu32 " Hz/%" PRIu16 " ch differs from the negotiated %" PRIu32
531 " Hz/%" PRIu16 " ch, encoded audio will be wrong",
532 srcFormat->nSamplesPerSec, srcFormat->nChannels, mc->sampleRate, mc->channels);
533 }
534
535 const BYTE* inPtr = data;
536 size_t inRemaining = length;
537
538 while (inRemaining > 0)
539 {
540 /* Fill the accumulation buffer up to 1024 frames */
541 const size_t needed = mc->encFrameBytes - mc->encBufferedBytes;
542 const size_t toCopy = (inRemaining < needed) ? inRemaining : needed;
543
544 memcpy(mc->encBuffer + mc->encBufferedBytes, inPtr, toCopy);
545 mc->encBufferedBytes += toCopy;
546 inPtr += toCopy;
547 inRemaining -= toCopy;
548
549 if (mc->encBufferedBytes == mc->encFrameBytes)
550 {
551 /* Exactly 1024 frames ready: queue to MediaCodec. Drop the frame on failure, a
552 * retained accumulator would be re-fed and duplicate audio. */
553 const BOOL fed = mediacodec_feed_buffer(mc, mc->encBuffer, mc->encFrameBytes,
554 AAC_FRAME_SAMPLES, out);
555 mc->encBufferedBytes = 0;
556 if (!fed)
557 return FALSE;
558 }
559 }
560
561 return TRUE;
562}
563
564BOOL mediacodec_aac_dsp_decode(FREERDP_DSP_COMMON_CONTEXT* context, const AUDIO_FORMAT* srcFormat,
565 const BYTE* data, size_t length, wStream* out)
566{
567 WINPR_ASSERT(context);
568 WINPR_ASSERT(srcFormat);
569
570 if (srcFormat->wFormatTag != WAVE_FORMAT_AAC_MS)
571 {
572 WLog_WARN(TAG, "expected AAC input, got 0x%04x", (unsigned)srcFormat->wFormatTag);
573 return FALSE;
574 }
575
576 MEDIACODEC_AAC* mc = (MEDIACODEC_AAC*)context->mediacodecAacInstance;
577 if (!mc || !mc->codec || !mc->started || !data || length == 0 || !out)
578 return FALSE;
579
580 /* In AAC decode, one AU is typically 1024 output frames */
581 return mediacodec_feed_buffer(mc, data, length, AAC_FRAME_SAMPLES, out);
582}
583
584BOOL mediacodec_aac_dsp_supports_format(const AUDIO_FORMAT* format, BOOL encode)
585{
586 WINPR_ASSERT(format);
587
588 if (format->wFormatTag != WAVE_FORMAT_AAC_MS)
589 return FALSE;
590
591 if ((format->nSamplesPerSec == 0) || (format->nSamplesPerSec > AAC_MAX_SAMPLE_RATE) ||
592 (format->nChannels < 1) || (format->nChannels > 8))
593 return FALSE;
594
595 if (encode && (format->nChannels > 2))
596 return FALSE;
597
598 if (!encode)
599 {
600 const BYTE* asc = nullptr;
601 size_t ascSize = 0;
602
603 if (aac_format_asc(format, &asc, &ascSize))
604 return TRUE;
605 }
606
607 /* Without a sampling frequency index there is no AudioSpecificConfig to configure with,
608 * and advertising the format would only fail later when the device is opened. */
609 return aac_sample_rate_index(format->nSamplesPerSec) >= 0;
610}