AAudio

AAudio là một API Android C mới được ra mắt trong bản phát hành Android O. API này được thiết kế cho các ứng dụng âm thanh hiệu suất cao yêu cầu độ trễ thấp. Các ứng dụng giao tiếp với AAudio bằng cách đọc và ghi dữ liệu vào các luồng dữ liệu.

AAudio API có thiết kế tối giản và không thực hiện những chức năng sau:

  • Liệt kê thiết bị âm thanh
  • Định tuyến tự động giữa các thiết bị đầu cuối âm thanh
  • Thao tác I/O đối với tệp
  • Giải mã âm thanh nén
  • Trình bày tự động tất cả các đầu vào/luồng trong một lệnh gọi lại.

Bắt đầu

Bạn có thể gọi AAudio từ mã C++. Để thêm tập tính chất AAudio vào ứng dụng của bạn, hãy thêm tệp tiêu đề AAudio.h:

#include <aaudio/AAudio.h>

Luồng âm thanh

AAudio di chuyển dữ liệu âm thanh giữa ứng dụng và các đầu vào cũng như đầu ra âm thanh trên thiết bị Android. Ứng dụng của bạn truyền dữ liệu vào và ra bằng cách đọc và ghi vào luồng âm thanh, được biểu thị bằng cấu trúc AAudioStream. Các lệnh gọi đọc/ghi có thể là lệnh tuần tự hoặc không tuần tự.

Một luồng được xác định bởi những đặc điểm sau đây:

  • Thiết bị âm thanh là nguồn hoặc bồn lưu trữ dữ liệu cho dữ liệu trong luồng.
  • Chế độ chia sẻ xác định liệu một luồng có quyền truy cập độc quyền vào một thiết bị âm thanh có thể được chia sẻ giữa nhiều luồng hay không.
  • Định dạng của dữ liệu âm thanh trong luồng.

Thiết bị âm thanh

Mỗi luồng được gắn với một thiết bị âm thanh duy nhất.

Thiết bị âm thanh là giao diện phần cứng hoặc thiết bị đầu cuối ảo hoạt động như một nguồn hoặc bồn lưu trữ dữ liệu để truyền dữ liệu âm thanh kỹ thuật số liên tục. Đừng nhầm lẫn thiết bị âm thanh(tai nghe Bluetooth hoặc micrô tích hợp) với thiết bị Android (điện thoại hoặc đồng hồ đeo tay) đang chạy ứng dụng của bạn.

Bạn có thể dùng phương thức AudioManager getDevices() để phát hiện các thiết bị âm thanh có trên thiết bị Android của mình. Phương thức này sẽ trả về thông tin liên quan đến type của mỗi thiết bị.

Mỗi thiết bị âm thanh có một mã nhận dạng duy nhất trên thiết bị Android. Bạn có thể sử dụng mã này để liên kết một luồng âm thanh với một thiết bị âm thanh cụ thể. Tuy nhiên, trong hầu hết các trường hợp, bạn có thể để AAudio chọn thiết bị chính mặc định thay vì tự chỉ định.

Thiết bị âm thanh đi kèm với một luồng sẽ xác định liệu luồng đó dành cho dữ liệu vào hay ra. Một luồng chỉ có thể di chuyển dữ liệu theo một hướng. Khi xác định một luồng, bạn cũng đặt hướng cho luồng đó. Khi bạn mở một luồng, Android sẽ kiểm tra để đảm bảo rằng thiết bị âm thanh và hướng của luồng thích hợp với nhau.

Chế độ chia sẻ

Luồng có chế độ chia sẻ:

  • AAUDIO_SHARING_MODE_EXCLUSIVE nghĩa là luồng có quyền truy cập độc quyền đối với thiết bị âm thanh của luồng; mọi luồng âm thanh khác đều không dùng được thiết bị này. Nếu thiết bị âm thanh đang được sử dụng, thì có thể luồng không có được quyền truy cập độc quyền. Các luồng dành riêng có thể có độ trễ thấp hơn, nhưng cũng có nhiều khả năng bị ngắt kết nối hơn. Bạn nên đóng các luồng dành riêng ngay khi không cần các luồng đó nữa, để các ứng dụng khác có thể truy cập vào thiết bị. Luồng dành riêng có độ trễ thấp nhất có thể.
  • AAUDIO_SHARING_MODE_SHARED cho phép AAudio kết hợp âm thanh. AAudio kết hợp tất cả các luồng dùng chung được gán cho cùng một thiết bị.

Bạn có thể đặt rõ chế độ chia sẻ khi tạo luồng. Theo mặc định, chế độ chia sẻ là SHARED.

Định dạng âm thanh

Dữ liệu truyền qua luồng có các thuộc tính âm thanh kỹ thuật số thông thường. Các thuộc tính này như sau:

  • Định dạng dữ liệu mẫu
  • Số kênh (số mẫu trên mỗi khung)
  • Tốc độ lấy mẫu

AAudio cho phép các định dạng mẫu sau:

aaudio_format_t Loại dữ liệu C Ghi chú
AAUDIO_FORMAT_PCM_I16 int16_t mẫu 16 bit phổ biến, định dạng Q0.15
AAUDIO_FORMAT_PCM_FLOAT độ chính xác đơn -1,0 đến +1,0
AAUDIO_FORMAT_PCM_I24_PACKED uint8_t theo nhóm 3 mẫu 24 bit được đóng gói, định dạng Q0.23
AAUDIO_FORMAT_PCM_I32 int32_t mẫu 32 bit phổ biến, định dạng Q0.31
AAUDIO_FORMAT_IEC61937 uint8_t âm thanh nén được gói trong IEC61937 để truyền qua HDMI hoặc S/PDIF

Nếu bạn yêu cầu một định dạng mẫu cụ thể, thì luồng sẽ sử dụng định dạng đó, ngay cả khi định dạng đó không phải là định dạng tối ưu cho thiết bị. Nếu bạn không chỉ định định dạng mẫu, thì AAudio sẽ chọn một định dạng tối ưu. Sau khi mở luồng, bạn phải truy vấn định dạng dữ liệu mẫu rồi chuyển đổi dữ liệu nếu cần, như trong ví dụ sau:

aaudio_format_t dataFormat = AAudioStream_getDataFormat(stream);
//... later
if (dataFormat == AAUDIO_FORMAT_PCM_I16) {
     convertFloatToPcm16(...)
}

Tạo luồng âm thanh

Thư viện AAudio tuân theo mẫu thiết kế của trình tạo và cung cấp AAudioStreamBuilder.

  1. Tạo AAudioStreamBuilder:

    AAudioStreamBuilder *builder;
    aaudio_result_t result = AAudio_createStreamBuilder(&builder);
    

  2. Đặt cấu hình luồng âm thanh trong trình tạo, sử dụng các hàm trình tạo tương ứng với tham số của luồng. Hiện có các hàm tập hợp không bắt buộc sau:

    AAudioStreamBuilder_setDeviceId(builder, deviceId);
    AAudioStreamBuilder_setDirection(builder, direction);
    AAudioStreamBuilder_setSharingMode(builder, mode);
    AAudioStreamBuilder_setSampleRate(builder, sampleRate);
    AAudioStreamBuilder_setChannelCount(builder, channelCount);
    AAudioStreamBuilder_setFormat(builder, format);
    AAudioStreamBuilder_setBufferCapacityInFrames(builder, frames);
    

    Lưu ý rằng các phương thức này không báo cáo lỗi, chẳng hạn như một hằng số không xác định hoặc giá trị nằm ngoài phạm vi.

    Nếu bạn không chỉ định mã thiết bị, giá trị mặc định sẽ là thiết bị đầu ra chính. Nếu bạn không chỉ định hướng luồng, giá trị mặc định sẽ là luồng đầu ra. Đối với tất cả các tham số khác, bạn có thể đặt giá trị một cách rõ ràng hoặc để hệ thống chỉ định giá trị tối ưu bằng cách không chỉ định tham số nào hoặc đặt tham số đó thành AAUDIO_UNSPECIFIED.

    Để đảm bảo an toàn, hãy kiểm tra trạng thái của luồng âm thanh sau khi bạn tạo, như được giải thích trong bước 4 bên dưới.

  3. Khi AAudioStreamBuilder được định cấu hình, hãy sử dụng công cụ này để tạo luồng:

    AAudioStream *stream;
    result = AAudioStreamBuilder_openStream(builder, &stream);
    

  4. Sau khi tạo luồng, hãy xác minh cấu hình của luồng. Nếu bạn đã chỉ định định dạng mẫu, tốc độ lấy mẫu hoặc tỷ lệ mẫu trên mỗi khung thì các giá trị này sẽ không thay đổi. Nếu bạn đã chỉ định chế độ chia sẻ hoặc dung lượng vùng đệm, thì các giá trị này có thể thay đổi tuỳ vào khả năng của thiết bị âm thanh của luồng và thiết bị Android mà thiết bị âm thanh chạy trên đó. Để lập trình phòng thủ hiệu quả, bạn nên kiểm tra cấu hình của luồng trước khi sử dụng. Có các hàm để truy xuất chế độ cài đặt luồng tương ứng với từng chế độ cài đặt của trình tạo:

    AAudioStreamBuilder_setDeviceId() AAudioStream_getDeviceId()
    AAudioStreamBuilder_setDirection() AAudioStream_getDirection()
    AAudioStreamBuilder_setSharingMode() AAudioStream_getSharingMode()
    AAudioStreamBuilder_setSampleRate() AAudioStream_getSampleRate()
    AAudioStreamBuilder_setChannelCount() AAudioStream_getChannelCount()
    AAudioStreamBuilder_setFormat() AAudioStream_getFormat()
    AAudioStreamBuilder_setBufferCapacityInFrames() AAudioStream_getBufferCapacityInFrames()

  5. Bạn có thể lưu trình tạo và sử dụng lại trong tương lai để tạo nhiều luồng hơn. Tuy nhiên, nếu không có ý định sử dụng trình tạo này nữa, thì bạn nên xoá đi.

    AAudioStreamBuilder_delete(builder);
    

Sử dụng luồng âm thanh

Chuyển đổi trạng thái

Luồng AAudio thường có một trong năm trạng thái ổn định (trạng thái lỗi Disconnected (Đã ngắt kết nối) sẽ được mô tả ở cuối phần này):

  • Open (Mở)
  • Started (Đã khởi động)
  • Paused (Đã tạm dừng)
  • Flushed (Đã xoá)
  • Stopped (Đã dừng)

Dữ liệu chỉ lưu chuyển qua một luồng khi luồng đó đang ở trạng thái Started (Đã khởi động) Để chuyển luồng giữa các trạng thái, hãy sử dụng một trong các hàm yêu cầu chuyển đổi trạng thái:

aaudio_result_t result;
result = AAudioStream_requestStart(stream);
result = AAudioStream_requestStop(stream);
result = AAudioStream_requestPause(stream);
result = AAudioStream_requestFlush(stream);

Lưu ý: Bạn chỉ có thể yêu cầu tạm dừng hoặc xoá đối với luồng đầu ra:

Các hàm này không đồng bộ và quá trình thay đổi trạng thái không xảy ra ngay lập tức. Khi bạn yêu cầu thay đổi trạng thái, luồng sẽ di chuyển một trong các trạng thái tạm thời tương ứng:

  • Starting (Đang khởi động)
  • Pausing (Đang tạm dừng)
  • Pausing (Đang xoá)
  • Pausing (Đang dừng)
  • Closing (Đang đóng)

Sơ đồ dưới đây cho thấy các trạng thái ổn định ở dạng hình chữ nhật bo tròn cạnh và các trạng thái tạm thời dưới dạng hình chữ nhật bằng nét đứt. Mặc dù không được trình bày trong biểu đồ, nhưng bạn có thể thực hiện lệnh gọi close() từ bất kỳ trạng thái nào

Vòng đời của AAudio

AAudio không cung cấp lệnh gọi lại để cảnh báo bạn về việc thay đổi trạng thái. Một hàm đặc biệt, AAudioStream_waitForStateChange(stream, inputState, nextState, timeout), có thể được dùng để chờ thay đổi trạng thái.

Hàm này không tự phát hiện sự thay đổi trạng thái và không chờ một trạng thái cụ thể. Hàm này sẽ chờ cho đến khi trạng thái hiện tại khác với inputState mà bạn chỉ định.

Ví dụ: Sau khi yêu cầu tạm dừng, một luồng sẽ ngay lập tức chuyển sang trạng thái Pausing (Đang tạm dừng) và đến một lúc nào đó sẽ chuyển sang trạng thái Paused (Đã tạm dừng) – tuy nhiên, không có gì đảm bảo điều này. Vì bạn không thể đợi trạng thái Paused (Đã tạm dừng), hãy sử dụng waitForStateChange() để đợi bất kỳ trạng thái nào khác ngoài trạng thái Pausing (Đang tạm dừng). Bạn có thể thực hiện như sau:

aaudio_stream_state_t inputState = AAUDIO_STREAM_STATE_PAUSING;
aaudio_stream_state_t nextState = AAUDIO_STREAM_STATE_UNINITIALIZED;
int64_t timeoutNanos = 100 * AAUDIO_NANOS_PER_MILLISECOND;
result = AAudioStream_requestPause(stream);
result = AAudioStream_waitForStateChange(stream, inputState, &nextState, timeoutNanos);

Nếu trạng thái của luồng không là Pausing (Đang tạm dừng) (inputState mà chúng tôi giả định là trạng thái hiện tại tại thời điểm thực hiện lệnh gọi), thì hàm sẽ trả về ngay lập tức. Nếu không, hàm sẽ chặn cho đến khi trạng thái không còn là Pausing (Đang tạm dừng) hoặc đến khi hết thời gian chờ. Khi hàm trả về, tham số nextState sẽ cho biết trạng thái hiện tại của luồng.

Bạn có thể sử dụng kỹ thuật này sau khi thực hiện lệnh gọi để bắt đầu, dừng hoặc xoá, bằng cách sử dụng trạng thái tạm thời tương ứng làm inputState. Không gọi waitForStateChange() sau khi gọi AAudioStream_close() vì luồng sẽ bị xoá ngay khi đóng. Không gọi AAudioStream_close() trong khi waitForStateChange() đang chạy trong một chuỗi khác.

Đọc và ghi vào luồng âm thanh

Có hai cách xử lý dữ liệu trong luồng sau khi luồng bắt đầu:

Để ghi hoặc đọc theo tuần tự có thể chuyển số lượng khung được chỉ định, hãy đặt timeoutNanos lớn hơn 0. Đối với lệnh gọi không tuần tự, hãy đặt timeoutNanos thành 0. Trong trường hợp này, kết quả là số khung thực tế được chuyển.

Khi đọc dữ liệu đầu vào, bạn nên xác minh số lượng khung chính xác đã được đọc. Nếu không, vùng đệm có thể chứa dữ liệu không xác định có khả năng gây ra sự cố âm thanh. Bạn có thể lót thêm (pad) cho vùng đệm bằng nhiều số 0 để tạo kỹ thuật silent dropout (bỏ qua thầm lặng):

aaudio_result_t result =
    AAudioStream_read(stream, audioData, numFrames, timeout);
if (result < 0) {
  // Error!
}
if (result != numFrames) {
  // pad the buffer with zeros
  memset(static_cast<sample_type*>(audioData) + result * samplesPerFrame, 0,
      sizeof(sample_type) * (numFrames - result) * samplesPerFrame);
}

Bạn có thể trang bị cho bộ đệm của luồng trước khi bắt đầu khởi động luồng bằng cách ghi dữ liệu hoặc ghi khoảng im lặng vào luồng đó. Bạn phải thực hiện thao tác này trong lệnh gọi không tuần tự với timeoutNanos được đặt thành 0.

Dữ liệu trong vùng đệm phải khớp với định dạng dữ liệu do AAudioStream_getDataFormat() trả về.

Đóng luồng âm thanh

Khi bạn sử dụng xong, hãy đóng luồng:

AAudioStream_close(stream);

Sau khi đóng luồng, bạn không thể sử dụng luồng đó với bất kỳ chức năng dựa trên luồng nào của AAudio.

Luồng âm thanh bị ngắt kết nối

Một luồng âm thanh có thể bị ngắt kết nối bất cứ lúc nào nếu một trong những sự kiện sau xảy ra:

  • Thiết bị âm thanh đã liên kết không còn được kết nối nữa (ví dụ: đã rút giắc cắm tháo tai nghe).
  • Đã xảy ra lỗi nội bộ.
  • Thiết bị âm thanh không còn là thiết bị âm thanh chính nữa.

Khi bị ngắt kết nối, luồng có trạng thái "Disconnected" (Đã ngắt kết nối) và mọi nỗ lực thực thi AAudioStream_write() hoặc các hàm khác sẽ trả về lỗi. Bạn luôn phải dừng và đóng luồng đã ngắt kết nối, bất kể mã lỗi là gì.

Nếu đang sử dụng lệnh gọi lại dữ liệu (trái ngược với một trong các phương thức đọc/ghi trực tiếp), thì bạn sẽ không nhận được bất kỳ mã trả về nào khi luồng bị ngắt kết nối. Để nhận thông báo khi điều này xảy ra, hãy ghi một hàm AAudioStream_errorCallback và đăng ký hàm đó bằng AAudioStreamBuilder_setErrorCallback().

Nếu bạn nhận được thông báo về việc ngắt kết nối trong chuỗi lệnh gọi lại lỗi, thì bạn phải dừng và đóng luồng từ một chuỗi khác. Nếu không, bạn có thể gặp tình huống tắc nghẽn.

Lưu ý: Nếu bạn mở một luồng mới, thì luồng đó có thể có các chế độ cài đặt khác với luồng gốc (ví dụ như framesPerBurst):

void errorCallback(AAudioStream *stream,
                   void *userData,
                   aaudio_result_t error) {
    // Launch a new thread to handle the disconnect.
    std::thread myThread(my_error_thread_proc, stream, userData);
    myThread.detach(); // Don't wait for the thread to finish.
}

Tối ưu hoá hiệu suất

Bạn có thể tối ưu hoá hiệu suất của ứng dụng âm thanh bằng cách điều chỉnh vùng đệm nội bộ và sử dụng các chuỗi đặc biệt có mức độ ưu tiên cao.

Điều chỉnh vùng đệm để giảm thiểu độ trễ

AAudio truyền dữ liệu vào và ra khỏi các vùng đệm nội bộ mà AAudio duy trì, mỗi vùng đệm cho một thiết bị âm thanh.

Dung lượng của vùng đệm là tổng lượng dữ liệu mà một vùng đệm có thể chứa. Bạn có thể gọi AAudioStreamBuilder_setBufferCapacityInFrames() để đặt dung lượng. Phương thức này giới hạn dung lượng mà bạn có thể phân bổ tới giá trị tối đa mà thiết bị cho phép. Sử dụng AAudioStream_getBufferCapacityInFrames() để xác minh dung lượng thực tế của vùng đệm.

Ứng dụng không phải sử dụng toàn bộ dung lượng của vùng đệm. AAudio lấp đầy vùng đệm đến kích thước mà bạn có thể đặt. Kích thước của vùng đệm có thể không lớn hơn dung lượng và thường nhỏ hơn. Bằng cách kiểm soát kích thước vùng đệm, bạn xác định số gói (burst) dữ liệu nạp cần để lấp đầy vùng đệm và do đó, kiểm soát được độ trễ. Sử dụng các phương thức AAudioStreamBuilder_setBufferSizeInFrames()AAudioStreamBuilder_getBufferSizeInFrames() cho phù hợp với kích thước vùng đệm.

Khi phát âm thanh, ứng dụng sẽ ghi vào vùng đệm và chặn cho đến khi quá trình ghi hoàn tất. AAudio đọc từ vùng đệm trong những gói tuỳ ý. Mỗi gói chứa nhiều khung âm thanh và thường nhỏ hơn kích thước của vùng đệm được đọc. Hệ thống điều khiển kích thước và tốc độ của gói, những thuộc tính này thường do chính mạch của thiết bị âm thanh chỉ định. Mặc dù không thể thay đổi kích thước hoặc tốc độ của gói, bạn có thể đặt kích thước của vùng đệm nội bộ theo số gói của vùng đệm đó. Nói chung, bạn sẽ có được độ trễ thấp nhất nếu dung lượng vùng đệm của AAudioStream là bội số của dung lượng gói được báo cáo.

      Lưu AAudio vào vùng đệm

Một trong những cách tối ưu hoá dung lượng vùng đệm là bắt đầu từ một vùng đệm lớn và giảm dần cho đến khi quá trình chạy ngầm bắt đầu, sau đó nhắc vùng đệm sao lưu. Ngoài ra, bạn có thể bắt đầu từ dung lượng vùng đệm nhỏ và nếu có được quá trình chạy ngầm từ đó, hãy tăng dung lượng vùng đệm cho đến khi đầu ra lưu chuyển dễ dàng trở lại.

Quá trình này có thể diễn ra rất nhanh, trước cả khi người dùng phát âm thanh đầu tiên. Bạn nên thực hiện việc xác định dung lượng vùng đệm ban đầu trước, sử dụng khoảng lặng, để người dùng không nghe thấy bất kỳ sự cố âm thanh nào. Hiệu suất của hệ thống có thể thay đổi theo thời gian (ví dụ: người dùng có thể tắt chế độ trên máy bay). Vì tính năng điều chỉnh vùng đệm có mức hao tổn rất thấp , nên ứng dụng của bạn có thể thực hiện điều chỉnh liên tục trong khi ứng dụng đọc hoặc ghi dữ liệu vào luồng.

Sau đây là ví dụ về vòng lặp tối ưu hoá vùng đệm:

int32_t previousUnderrunCount = 0;
int32_t framesPerBurst = AAudioStream_getFramesPerBurst(stream);
int32_t bufferSize = AAudioStream_getBufferSizeInFrames(stream);

int32_t bufferCapacity = AAudioStream_getBufferCapacityInFrames(stream);

while (go) {
    result = writeSomeData();
    if (result < 0) break;

    // Are we getting underruns?
    if (bufferSize < bufferCapacity) {
        int32_t underrunCount = AAudioStream_getXRunCount(stream);
        if (underrunCount > previousUnderrunCount) {
            previousUnderrunCount = underrunCount;
            // Try increasing the buffer size by one burst
            bufferSize += framesPerBurst;
            bufferSize = AAudioStream_setBufferSize(stream, bufferSize);
        }
    }
}

Sử dụng kỹ thuật này để tối ưu hoá dung lượng vùng đệm cho luồng đầu vào không mang lại lợi ích gì. Luồng đầu vào chạy nhanh nhất có thể, cố gắng giữ cho lượng dữ liệu lưu vào vùng đệm ở mức tối thiểu và sau đó lấp đầy khi ứng dụng bị giành quyền.

Sử dụng lệnh gọi lại có mức độ ưu tiên cao

Nếu ứng dụng của bạn đọc hoặc ghi dữ liệu âm thanh từ một chuỗi thông thường, thì ứng dụng đó có thể bị giành quyền hoặc gặp phải vấn đề về sự biến động thời gian. Việc này có thể gây ra sự cố âm thanh. Việc sử dụng vùng đệm lớn hơn có thể giúp đề phòng các sự cố như vậy, nhưng vùng đệm lớn cũng gây độ trễ âm thanh dài hơn. Đối với các ứng dụng yêu cầu độ trễ thấp, luồng âm thanh có thể sử dụng hàm callback không đồng bộ để chuyển dữ liệu đến và đi khỏi ứng dụng của bạn. AAudio thực thi lệnh gọi lại trong một chuỗi có mức ưu tiên cao hơn có hiệu suất tốt hơn.

Hàm callback có nguyên mẫu sau:

typedef aaudio_data_callback_result_t (*AAudioStream_dataCallback)(
        AAudioStream *stream,
        void *userData,
        void *audioData,
        int32_t numFrames);

Sử dụng việc xây dựng luồng để đăng ký lệnh gọi lại:

AAudioStreamBuilder_setDataCallback(builder, myCallback, myUserData);

Trong trường hợp đơn giản nhất, luồng sẽ thực thi hàm callback theo định kỳ để thu nạp dữ liệu cho gói tiếp theo.

Hàm callback không được thực hiện lệnh đọc hoặc ghi trên luồng gọi hàm. Nếu lệnh gọi lại thuộc về luồng đầu vào, mã của bạn phải xử lý dữ liệu được cung cấp trong vùng đệm audioData (được chỉ định là đối số thứ ba). Nếu lệnh gọi lại thuộc về một luồng đầu ra, mã của bạn sẽ đặt dữ liệu vào vùng đệm.

Ví dụ: Bạn có thể sử dụng lệnh gọi lại để liên tục tạo đầu ra sóng hình sin như sau:

aaudio_data_callback_result_t myCallback(
        AAudioStream *stream,
        void *userData,
        void *audioData,
        int32_t numFrames) {
    int64_t timeout = 0;

    // Write samples directly into the audioData array.
    generateSineWave(static_cast<float *>(audioData), numFrames);
    return AAUDIO_CALLABCK_RESULT_CONTINUE;
}

Bạn có thể xử lý nhiều luồng bằng AAudio. Bạn có thể sử dụng một luồng làm luồng chính và truyền các biến con trỏ (pointer) đến các luồng khác trong dữ liệu người dùng. Đăng ký lệnh gọi lại cho luồng chính. Sau đó, sử dụng I/O không tuần tự trên các luồng khác. Dưới đây là ví dụ về lệnh gọi lại khứ hồi truyền luồng đầu vào đến luồng đầu ra. Luồng đầu ra là luồng gọi chính. Luồng đầu vào có trong dữ liệu người dùng.

Lệnh gọi lại thực hiện thao tác đọc không tuần tự từ luồng đầu vào, đặt dữ liệu vào vùng đệm của luồng đầu ra:

aaudio_data_callback_result_t myCallback(
        AAudioStream *stream,
        void *userData,
        void *audioData,
        int32_t numFrames) {
    AAudioStream *inputStream = (AAudioStream *) userData;
    int64_t timeout = 0;
    aaudio_result_t result =
        AAudioStream_read(inputStream, audioData, numFrames, timeout);

  if (result == numFrames)
      return AAUDIO_CALLABCK_RESULT_CONTINUE;
  if (result >= 0) {
      memset(static_cast<sample_type*>(audioData) + result * samplesPerFrame, 0,
          sizeof(sample_type) * (numFrames - result) * samplesPerFrame);
      return AAUDIO_CALLBACK_RESULT_CONTINUE;
  }
  return AAUDIO_CALLBACK_RESULT_STOP;
}

Lưu ý: Trong ví dụ này, hệ thống giả định rằng các luồng đầu vào và đầu ra có cùng số kênh, định dạng và tốc độ lấy mẫu. Định dạng của các luồng có thể không khớp nhau, miễn là mã xử lý bản dịch đúng cách.

Đặt chế độ hiệu suất

Mỗi AAudioStream đều có chế độ hiệu suất ảnh hưởng lớn đến hành vi của ứng dụng. Có 3 chế độ:

  • AAUDIO_PERFORMANCE_MODE_NONE là chế độ mặc định. Chế độ này sử dụng một luồng cơ bản để cân bằng giữa độ trễ và khả năng tiết kiệm pin.
  • AAUDIO_PERFORMANCE_MODE_LOW_LATENCY sử dụng vùng đệm nhỏ hơn và đường dẫn dữ liệu được tối ưu hoá để giảm độ trễ.
  • AAUDIO_PERFORMANCE_MODE_POWER_SAVING sử dụng vùng đệm nội bộ lớn hơn và đường dẫn dữ liệu giúp đánh đổi độ trễ lấy mức sử dụng pin thấp hơn.

Bạn có thể chọn chế độ hiệu suất bằng cách gọi setPerformanceMode(), rồi khám phá chế độ hiện tại bằng cách gọi getPerformanceMode().

Nếu độ trễ thấp quan trọng hơn việc tiết kiệm pin trong ứng dụng của bạn, hãy sử dụng AAUDIO_PERFORMANCE_MODE_LOW_LATENCY. Lựa chọn này hữu ích cho các ứng dụng mang tính tương tác cao, chẳng hạn như trò chơi hoặc đàn synthesizer có bàn phím.

Nếu tính năng tiết kiệm pin quan trọng hơn độ trễ thấp trong ứng dụng của bạn, hãy sử dụng AAUDIO_PERFORMANCE_MODE_POWER_SAVING. Đây là trường hợp thường gặp đối với những ứng dụng phát nhạc đã tạo trước đây, chẳng hạn như phát trực tiếp âm thanh hoặc trình phát tệp MIDI.

Trong phiên bản AAudio hiện tại, để có độ trễ thấp nhất có thể, bạn phải dùng chế độ hiệu suất AAUDIO_PERFORMANCE_MODE_LOW_LATENCY cùng với lệnh gọi lại có mức độ ưu tiên cao. Hãy làm theo ví dụ sau:

// Create a stream builder
AAudioStreamBuilder *streamBuilder;
AAudio_createStreamBuilder(&streamBuilder);
AAudioStreamBuilder_setDataCallback(streamBuilder, dataCallback, nullptr);
AAudioStreamBuilder_setPerformanceMode(streamBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);

// Use it to create the stream
AAudioStream *stream;
AAudioStreamBuilder_openStream(streamBuilder, &stream);

Độ an toàn cho chuỗi

API AAudio không hoàn toàn an toàn cho chuỗi. Bạn không thể gọi đồng thời một số chức năng AAudio từ nhiều chuỗi. Điều này là do AAudio tránh sử dụng mutex, cơ chế có thể gây ra sự cố và hiện tượng giành quyền trên chuỗi.

Để đảm bảo an toàn, không gọi AAudioStream_waitForStateChange() hoặc đọc hay ghi vào cùng một luồng từ hai chuỗi khác nhau. Tương tự như vậy, không đóng luồng trong một chuỗi trong khi đọc hoặc ghi vào luồng này trong chuỗi khác.

Những lệnh gọi trả về chế độ cài đặt luồng, chẳng hạn như AAudioStream_getSampleRate()AAudioStream_getChannelCount(), an toàn với chuỗi.

Những lệnh gọi này cũng an toàn với chuỗi:

  • AAudio_convert*ToText()
  • AAudio_createStreamBuilder()
  • AAudioStream_get*() ngoại trừ AAudioStream_getTimestamp()

Vấn đề đã biết

  • Độ trễ âm thanh ở mức cao đối với ghi tuần tự() vì bản phát hành Android O DP2 không sử dụng bản nhạc FAST. Hãy sử dụng lệnh gọi lại để giảm độ trễ.

Tài nguyên khác

Để tìm hiểu thêm, hãy tận dụng các tài nguyên sau đây:

Tài liệu tham khảo API

Lớp học lập trình

Video