Gỡ lỗi âm thanh

Bài viết này mô tả một số mẹo và thủ thuật để gỡ lỗi âm thanh Android.

Bồn rửa chén

"Tee sink" là tính năng gỡ lỗi của AudioFlinger, chỉ có trong các bản dựng tùy chỉnh, để giữ lại một đoạn âm thanh ngắn gần đây để phân tích sau này. Điều này cho phép so sánh giữa những gì thực sự được phát hoặc ghi lại với những gì được mong đợi.

Để đảm bảo quyền riêng tư, tee sink bị tắt theo mặc định, ở cả thời gian biên dịch và thời gian chạy. Để sử dụng bồn rửa tee, bạn sẽ cần kích hoạt nó bằng cách biên dịch lại và cũng bằng cách đặt thuộc tính. Đảm bảo tắt tính năng này sau khi bạn gỡ lỗi xong; không nên bật bồn rửa tee trong các bản dựng sản xuất.

Hướng dẫn trong phần này dành cho Android 7.x trở lên. Đối với Android 5.x và 6.x, hãy thay thế /data/misc/audioserver bằng /data/misc/media . Ngoài ra, bạn phải sử dụng bản dựng userdebug hoặc eng. Nếu bạn sử dụng bản dựng userdebug, hãy tắt tính xác thực bằng:

adb root && adb disable-verity && adb reboot

Thiết lập thời gian biên dịch

  1. cd frameworks/av/services/audioflinger
  2. Chỉnh sửa Configuration.h
  3. Bỏ ghi chú #define TEE_SINK .
  4. Xây dựng lại libaudioflinger.so .
  5. adb root
  6. adb remount
  7. Đẩy hoặc đồng bộ hóa libaudioflinger.so mới với /system/lib của thiết bị.

Thiết lập thời gian chạy

  1. adb shell getprop | grep ro.debuggable
    Xác nhận rằng đầu ra là: [ro.debuggable]: [1]
  2. adb shell
  3. ls -ld /data/misc/audioserver

    Xác nhận rằng đầu ra là:

    drwx------ media media ... media
    

    Nếu thư mục không tồn tại, hãy tạo nó như sau:

    mkdir /data/misc/audioserver
    chown media:media /data/misc/audioserver
    
  4. echo af.tee=# > /data/local.prop
    Trong đó giá trị af.tee là một số được mô tả bên dưới.
  5. chmod 644 /data/local.prop
  6. reboot

Giá trị cho thuộc tính af.tee

Giá trị của af.tee là một số từ 0 đến 7, biểu thị tổng của một số bit, mỗi bit cho một tính năng. Xem mã tại AudioFlinger::AudioFlinger() trong AudioFlinger.cpp để biết giải thích về từng bit, nhưng ngắn gọn:

  • 1 = đầu vào
  • 2 = Đầu ra FastMixer
  • 4 = AudioRecord và AudioTrack trên mỗi bản nhạc

Chưa có bit nào cho bộ đệm sâu hoặc bộ trộn thông thường, nhưng bạn có thể nhận được kết quả tương tự bằng cách sử dụng "4."

Kiểm tra và thu thập dữ liệu

  1. Chạy thử nghiệm âm thanh của bạn.
  2. adb shell dumpsys media.audio_flinger
  3. Hãy tìm một dòng trong đầu ra của dumpsys như thế này:
    tee copied to /data/misc/audioserver/20131010101147_2.wav
    Đây là tệp .wav PCM.
  4. Sau đó adb pull bất kỳ tệp /data/misc/audioserver/*.wav nào bạn quan tâm; lưu ý rằng tên tệp kết xuất dành riêng cho bản nhạc không xuất hiện trong đầu ra dumpsys nhưng vẫn được lưu vào /data/misc/audioserver khi đóng bản nhạc.
  5. Xem lại các tệp kết xuất để biết những lo ngại về quyền riêng tư trước khi chia sẻ với người khác.

Đề xuất

Hãy thử những ý tưởng này để có kết quả hữu ích hơn:

  • Tắt âm thanh chạm và bấm phím để giảm sự gián đoạn trong kết quả kiểm tra.
  • Tối đa hóa tất cả khối lượng.
  • Tắt các ứng dụng tạo ra âm thanh hoặc ghi âm từ micrô nếu chúng không được bạn quan tâm trong bài kiểm tra.
  • Các bãi chứa dành riêng cho đường đua chỉ được lưu khi đường đua bị đóng; bạn có thể cần phải buộc đóng một ứng dụng để hủy dữ liệu theo dõi cụ thể của ứng dụng đó
  • Thực hiện dumpsys ngay sau khi kiểm tra; có một lượng không gian ghi sẵn có hạn.
  • Để đảm bảo bạn không bị mất các tệp kết xuất, hãy tải chúng lên máy chủ của bạn theo định kỳ. Chỉ một số lượng tệp kết xuất có giới hạn được giữ lại; các bãi chứa cũ hơn sẽ bị loại bỏ sau khi đạt đến giới hạn đó.

Khôi phục

Như đã lưu ý ở trên, không nên bật tính năng phát bóng chìm. Khôi phục bản dựng và thiết bị của bạn như sau:

  1. Hoàn nguyên các thay đổi mã nguồn thành Configuration.h .
  2. Xây dựng lại libaudioflinger.so .
  3. Đẩy hoặc đồng bộ hóa libaudioflinger.so đã khôi phục với /system/lib của thiết bị.
  4. adb shell
  5. rm /data/local.prop
  6. rm /data/misc/audioserver/*.wav
  7. reboot

media.log

ALOGx macro

API ghi nhật ký ngôn ngữ Java tiêu chuẩn trong SDK Android là android.util.Log .

API ngôn ngữ C tương ứng trong Android NDK là __android_log_print được khai báo trong <android/log.h> .

Trong phần gốc của khung Android, chúng tôi thích các macro có tên ALOGE , ALOGW , ALOGI , ALOGV , v.v. Chúng được khai báo trong <utils/Log.h> và vì mục đích của bài viết này, chúng tôi sẽ gọi chung là ALOGx .

Tất cả các API này đều dễ sử dụng và dễ hiểu nên chúng có sức lan tỏa khắp nền tảng Android. Đặc biệt là quy trình mediaserver , bao gồm máy chủ âm thanh AudioFlinger, sử dụng rộng rãi ALOGx .

Tuy nhiên, có một số hạn chế đối với ALOGx và bạn bè:

  • Họ dễ bị "log spam": bộ đệm nhật ký là tài nguyên dùng chung nên có thể dễ dàng bị tràn do các mục nhật ký không liên quan, dẫn đến bỏ sót thông tin. Theo mặc định, biến thể ALOGV bị tắt vào thời gian biên dịch. Nhưng tất nhiên, thậm chí nó có thể dẫn đến spam nhật ký nếu nó được kích hoạt.
  • Các cuộc gọi hệ thống hạt nhân cơ bản có thể bị chặn, có thể dẫn đến đảo ngược mức độ ưu tiên và do đó làm xáo trộn và đo lường không chính xác. Đây là mối quan tâm đặc biệt đối với các luồng quan trọng về thời gian như FastMixerFastCapture .
  • Nếu một nhật ký cụ thể bị vô hiệu hóa để giảm spam trong nhật ký thì mọi thông tin lẽ ra được nhật ký đó ghi lại sẽ bị mất. Không thể kích hoạt lại một nhật ký cụ thể sau khi đã xác định rõ ràng rằng nhật ký đó sẽ rất thú vị.

NBLOG, media.log và MediaLogService

API NBLOG và quy trình media.log liên quan cũng như dịch vụ MediaLogService cùng nhau tạo thành một hệ thống ghi nhật ký mới hơn cho phương tiện và được thiết kế đặc biệt để giải quyết các vấn đề trên. Chúng tôi sẽ sử dụng thuật ngữ "media.log" một cách lỏng lẻo để chỉ cả ba, nhưng nói đúng ra NBLOG là API ghi nhật ký C++, media.log là tên quy trình Linux và MediaLogService là dịch vụ liên kết Android để kiểm tra nhật ký.

"Dòng thời gian" media.log là một chuỗi các mục nhật ký có thứ tự tương đối được giữ nguyên. Theo quy ước, mỗi luồng nên sử dụng dòng thời gian riêng.

Những lợi ích

Lợi ích của hệ thống media.log là:

  • Không spam nhật ký chính trừ khi và cho đến khi cần thiết.
  • Có thể được kiểm tra ngay cả khi mediaserver gặp sự cố hoặc bị treo.
  • Không bị chặn theo dòng thời gian.
  • Cung cấp ít sự xáo trộn hơn cho hiệu suất. (Tất nhiên không có hình thức ghi nhật ký nào là hoàn toàn không xâm phạm.)

Ngành kiến ​​​​trúc

Sơ đồ bên dưới thể hiện mối quan hệ giữa tiến trình mediaserver và tiến trình init , trước khi media.log được giới thiệu:

Kiến trúc trước media.log

Hình 1. Kiến trúc trước media.log

Điểm đáng chú ý:

  • init forks và execs mediaserver .
  • init phát hiện cái chết của mediaserver và phân nhánh lại nếu cần.
  • Ghi nhật ký ALOGx không được hiển thị.

Sơ đồ bên dưới hiển thị mối quan hệ mới của các thành phần, sau khi media.log được thêm vào kiến ​​trúc:

Kiến trúc sau media.log

Hình 2. Kiến trúc sau media.log

Những thay đổi quan trọng:

  • Khách hàng sử dụng API NBLOG để xây dựng các mục nhật ký và thêm chúng vào bộ đệm tròn trong bộ nhớ dùng chung.
  • MediaLogService có thể kết xuất nội dung của bộ đệm tròn bất kỳ lúc nào.
  • Bộ đệm tròn được thiết kế theo cách mà bất kỳ hỏng hóc nào của bộ nhớ dùng chung sẽ không gây ra sự cố MediaLogService và nó vẫn có thể kết xuất nhiều bộ đệm không bị ảnh hưởng bởi hỏng hóc.
  • Bộ đệm tròn không bị chặn và không khóa cho cả việc ghi các mục mới và đọc các mục hiện có.
  • Không yêu cầu lệnh gọi hệ thống hạt nhân để ghi hoặc đọc từ bộ đệm tròn (trừ dấu thời gian tùy chọn).

Sử dụng ở đâu

Kể từ Android 4.4, chỉ có một số điểm nhật ký trong AudioFlinger sử dụng hệ thống media.log . Mặc dù các API mới không dễ sử dụng như ALOGx nhưng chúng cũng không quá khó. Chúng tôi khuyến khích bạn tìm hiểu hệ thống ghi nhật ký mới cho những trường hợp không thể thiếu. Đặc biệt, nên sử dụng các luồng AudioFlinger phải chạy thường xuyên, định kỳ và không bị chặn như các luồng FastMixerFastCapture .

Cách sử dụng

Thêm nhật ký

Trước tiên, bạn cần thêm nhật ký vào mã của mình.

Trong các luồng FastMixerFastCapture , hãy sử dụng mã như sau:

logWriter->log("string");
logWriter->logf("format", parameters);
logWriter->logTimestamp();

Vì dòng thời gian NBLog này chỉ được sử dụng bởi các luồng FastMixerFastCapture nên không cần loại trừ lẫn nhau.

Trong các chủ đề AudioFlinger khác, hãy sử dụng mNBLogWriter :

mNBLogWriter->log("string");
mNBLogWriter->logf("format", parameters);
mNBLogWriter->logTimestamp();

Đối với các luồng không phải FastMixerFastCapture , dòng thời gian NBLog của luồng có thể được sử dụng bởi cả chính luồng đó và bởi các hoạt động liên kết. NBLog::Writer không cung cấp bất kỳ loại trừ lẫn nhau ngầm định nào trên mỗi dòng thời gian, vì vậy hãy đảm bảo rằng tất cả nhật ký xảy ra trong ngữ cảnh nơi giữ mutex mLock của luồng.

Sau khi bạn đã thêm nhật ký, hãy xây dựng lại AudioFlinger.

Thận trọng: Cần có dòng thời gian NBLog::Writer riêng biệt cho mỗi luồng, để đảm bảo an toàn cho luồng, vì các mốc thời gian bỏ qua các đột biến theo thiết kế. Nếu bạn muốn nhiều luồng sử dụng cùng một dòng thời gian, bạn có thể bảo vệ bằng một mutex hiện có (như được mô tả ở trên cho mLock ). Hoặc bạn có thể sử dụng trình bao bọc NBLog::LockedWriter thay vì NBLog::Writer . Tuy nhiên, điều này phủ nhận lợi ích chính của API này: hành vi không chặn của nó.

API NBLog đầy đủ có tại frameworks/av/include/media/nbaio/NBLog.h .

Kích hoạt media.log

media.log bị tắt theo mặc định. Nó chỉ hoạt động khi thuộc tính ro.test_harness1 . Bạn có thể kích hoạt nó bằng cách:

adb root
adb shell
echo ro.test_harness=1 > /data/local.prop
chmod 644 /data/local.prop
reboot

Kết nối bị mất trong quá trình khởi động lại, vì vậy:

adb shell
Lệnh ps media bây giờ sẽ hiển thị hai quy trình:
  • media.log
  • máy chủ trung gian

Lưu ý ID tiến trình của mediaserver để sử dụng sau.

Hiển thị các mốc thời gian

Bạn có thể yêu cầu kết xuất nhật ký theo cách thủ công bất cứ lúc nào. Lệnh này hiển thị nhật ký từ tất cả các mốc thời gian đang hoạt động và gần đây, sau đó xóa chúng:

dumpsys media.log

Lưu ý rằng theo thiết kế, các mốc thời gian là độc lập và không có cơ sở nào để hợp nhất các mốc thời gian.

Khôi phục nhật ký sau khi mediaserver chết

Bây giờ hãy thử tắt tiến trình mediaserver : kill -9 # , trong đó # là ID tiến trình bạn đã lưu ý trước đó. Bạn sẽ thấy kết xuất từ media.log trong logcat chính, hiển thị tất cả nhật ký dẫn đến sự cố.

dumpsys media.log