Bài viết này mô tả một số mẹo và thủ thuật để gỡ lỗi âm thanh trên Android.
Bồn rửa
"Tee sink" là một tính năng gỡ lỗi AudioFlinger, chỉ có trong các bản dựng tuỳ chỉnh, để giữ lại một đoạn âm thanh ngắn gần đây để phân tích sau. Điều này cho phép so sánh giữa nội dung thực sự được phát hoặc ghi lại với nội dung dự kiến.
Để bảo vệ quyền riêng tư, theo mặc định, tee sink sẽ bị tắt ở cả thời gian biên dịch và thời gian chạy. Để sử dụng tee sink, bạn cần bật tính năng này bằng cách biên dịch lại và đặt một thuộc tính. Hãy nhớ tắt tính năng này sau khi bạn gỡ lỗi xong; không được bật tee sink trong các bản dựng chính thức.
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 bản dựng eng. Nếu bạn sử dụng bản dựng userdebug, hãy tắt tính năng xác thực bằng:
adb root && adb disable-verity && adb reboot
Thiết lập tại thời điểm biên dịch
cd frameworks/av/services/audioflinger
- Chỉnh sửa
Configuration.h
. - Bỏ ghi chú
#define TEE_SINK
. - Tạo lại
libaudioflinger.so
. adb root
adb remount
- Đẩy hoặc đồng bộ hoá
libaudioflinger.so
mới với/system/lib
của thiết bị.
Thiết lập thời gian chạy
adb shell getprop | grep ro.debuggable
Xác nhận rằng kết quả là:[ro.debuggable]: [1]
adb shell
ls -ld /data/misc/audioserver
Xác nhận rằng kết quả là:
drwx------ media media ... media
Nếu thư mục không tồn tại, hãy tạo thư mục đó như sau:
mkdir /data/misc/audioserver
chown media:media /data/misc/audioserver
echo af.tee=# > /data/local.prop
Trong đó, giá trịaf.tee
là một số được mô tả bên dưới.chmod 644 /data/local.prop
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 đặc điểm.
Hãy xem mã tại AudioFlinger::AudioFlinger()
trong AudioFlinger.cpp
để biết nội dung giải thích ngắn gọn về từng bit:
- 1 = đầu vào
- 2 = Đầu ra FastMixer
- 4 = AudioRecord và AudioTrack trên mỗi bản âm thanh
Chưa có bit nào cho vùng đệ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 thử và thu thập dữ liệu
- Chạy kiểm thử âm thanh.
adb shell dumpsys media.audio_flinger
- Tìm một dòng trong kết quả
dumpsys
như sau:
tee copied to /data/misc/audioserver/20131010101147_2.wav
Đây là tệp .wav PCM. - Sau đó,
adb pull
mọi tệp/data/misc/audioserver/*.wav
mà bạn quan tâm; lưu ý rằng tên tệp báo lỗi cụ thể theo kênh không xuất hiện trong đầu radumpsys
, nhưng vẫn được lưu vào/data/misc/audioserver
khi đóng kênh. - Xem lại các tệp báo lỗi để biết các vấn đề về quyền riêng tư trước khi chia sẻ với người khác.
Nội dung đề xuất
Hãy thử những ý tưởng sau để có kết quả hữu ích hơn:
- Tắt âm thanh chạm và âm thanh nhấp phím để giảm sự gián đoạn trong kết quả kiểm thử.
- Tối đa hoá tất cả âm lượng.
- Tắt các ứng dụng phát âm thanh hoặc ghi âm từ micrô, nếu các ứng dụng đó không liên quan đến kiểm thử của bạn.
- Tệp báo lỗi theo dõi chỉ được lưu khi kênh đó đóng; bạn có thể cần phải buộc đóng một ứng dụng để kết xuất dữ liệu theo dõi của ứng dụng đó
- Thực hiện
dumpsys
ngay sau khi kiểm thử; có một lượng không gian ghi có hạn. - Để đảm bảo bạn không mất các tệp báo lỗi, hãy tải các tệp đó lên máy chủ lưu trữ theo định kỳ. Chỉ một số lượng tệp báo lỗi giới hạn được giữ lại; các tệp báo lỗi cũ hơn sẽ bị xoá sau khi đạt đến giới hạn đó.
Khôi phục
Như đã lưu ý ở trên, bạn không nên bật tính năng tee sink. Khôi phục bản dựng và thiết bị như sau:
- Huỷ bỏ các thay đổi đối với mã nguồn về
Configuration.h
. - Tạo lại
libaudioflinger.so
. - Đẩy hoặc đồng bộ hoá
libaudioflinger.so
đã khôi phục vào/system/lib
của thiết bị. adb shell
rm /data/local.prop
rm /data/misc/audioserver/*.wav
reboot
media.log
Macro ALOGx
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 ta ưu tiên các macro có tên là ALOGE
, ALOGW
, ALOGI
, ALOGV
, v.v. Các macro này được khai báo trong <utils/Log.h>
và cho mục đích của bài viết này, chúng ta sẽ gọi chung các macro này là ALOGx
.
Tất cả các API này đều dễ sử dụng và dễ hiểu, vì vậy, chúng được sử dụng rộng rãi trên nền tảng Android. Cụ thể, quy trình mediaserver
, bao gồm cả máy chủ âm thanh AudioFlinger, sử dụng rộng rãi ALOGx
.
Tuy nhiên, ALOGx
và bạn bè có một số hạn chế như sau:
-
Chúng dễ bị "nhật ký rác": vùng đệm nhật ký là một tài nguyên dùng chung nên dễ bị tràn do các mục nhập nhật ký không liên quan, dẫn đến việc bỏ lỡ thông tin. Theo mặc định, biến thể
ALOGV
bị tắt tại thời điểm biên dịch. Tuy nhiên, tất nhiên, ngay cả khi được bật, tính năng này cũng có thể dẫn đến nhật ký rác. -
Các lệnh gọi hệ thống hạt nhân cơ bản có thể chặn, có thể dẫn đến việc đảo ngược mức độ ưu tiên và do đó gây ra sự gián đoạn và không chính xác trong quá trình đo lường. Điều này đặc biệt quan trọng đối với các luồng quan trọng về thời gian như
FastMixer
vàFastCapture
. - Nếu một nhật ký cụ thể bị tắt để giảm nhật ký rác, thì mọi thông tin mà nhật ký đó đã ghi lại sẽ bị mất. Bạn không thể bật một nhật ký cụ thể theo cách hồi tố, sau khi bạn nhận thấy nhật ký đó sẽ rất thú vị.
NBLOG, media.log và MediaLogService
Các 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 nội dung nghe nhìn và được thiết kế riêng để giải quyết các vấn đề nêu trên. Chúng ta sẽ sử dụng thuật ngữ "media.log" để chỉ cả ba, nhưng nói một cách chính xác thì 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ý.
"Tiến trình" media.log
là một loạt các mục nhập nhật ký có thứ tự tương đối được giữ nguyên.
Theo quy ước, mỗi luồng phải sử dụng dòng thời gian riêng.
Lợi ích
Hệ thống media.log
có các lợi ích sau:
- Không làm hỏng nhật ký chính trừ khi cần thiết.
- Có thể kiểm tra ngay cả khi
mediaserver
gặp sự cố hoặc bị treo. - Không chặn theo dòng thời gian.
- Ít gây ảnh hưởng đến hiệu suất hơn. (Tất nhiên, không có hình thức ghi nhật ký nào hoàn toàn không gây phiền toái.)
Kiến trúc
Sơ đồ dưới đây cho thấy mối quan hệ giữa quy trình mediaserver
và quy trình init
, trước khi media.log
được giới thiệu:

Hình 1. Cấu trúc trước media.log
Các điểm đáng chú ý:
init
phân nhánh và thực thimediaserver
.init
phát hiện sự cố củamediaserver
và phân nhánh lại nếu cần.- Không hiển thị tính năng ghi nhật ký
ALOGx
.
Sơ đồ dưới đây cho thấy mối quan hệ mới của các thành phần sau khi thêm media.log
vào cấu trúc:

Hình 2. Cấu trúc sau media.log
Thay đổi quan trọng:
-
Ứng dụng sử dụng API
NBLOG
để tạo các mục nhập nhật ký và thêm các mục đó vào vùng đệm tròn trong bộ nhớ dùng chung. -
MediaLogService
có thể kết xuất nội dung của vùng đệm tròn bất cứ lúc nào. -
Vùng đệm tròn được thiết kế sao cho mọi lỗi hỏng của bộ nhớ dùng chung sẽ không làm
MediaLogService
gặp sự cố và vùng đệm vẫn có thể kết xuất nhiều dữ liệu nhất có thể mà không bị lỗi hỏng ảnh hưởng. - Vùng đệm tròn không chặn và không khoá cho cả việc ghi mục nhập mới và đọc mục nhập hiện có.
- Không cần lệnh gọi hệ thống hạt nhân để ghi vào hoặc đọc từ vùng đệm tròn (ngoài dấu thời gian không bắt buộc).
Nền tảng sử dụng
Kể từ Android 4.4, chỉ có một vài đ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 cũng không quá khó.
Bạn nên tìm hiểu hệ thống ghi nhật ký mới trong những trường hợp không thể thiếu hệ thống này.
Cụ thể, bạn nên sử dụng luồng AudioFlinger phải chạy thường xuyên, định kỳ và không bị chặn, chẳng hạn như luồng FastMixer
và FastCapture
.
How to use
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 luồng FastMixer
và FastCapture
, 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 các luồng FastMixer
và FastCapture
sử dụng, nên không cần loại trừ lẫn nhau.
Trong các luồng 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ác ngoài FastMixer
và FastCapture
, cả luồng và các thao tác liên kết đều có thể sử dụng tiến trình NBLog
của luồng. NBLog::Writer
không cung cấp bất kỳ tính năng loại trừ lẫn nhau ngầm ẩn nào trên mỗi tiến trình, vì vậy, hãy đảm bảo rằng tất cả nhật ký đều xảy ra trong ngữ cảnh mà mLock
của mutex của luồng được giữ lại.
Sau khi thêm nhật ký, hãy tạo lại AudioFlinger.
Thận trọng: Bạn cần có một dòng thời gian NBLog::Writer
riêng cho mỗi luồng để đảm bảo an toàn cho luồng, vì theo thiết kế, các dòng thời gian sẽ bỏ qua các mutex. Nếu muốn nhiều luồng sử dụng cùng một tiến trình, bạn có thể bảo vệ bằng một mutex hiện có (như 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 làm mất đi một lợi ích chính của API này: hành vi không chặn.
API NBLog
đầy đủ nằm ở frameworks/av/include/media/nbaio/NBLog.h
.
Bật media.log
media.log
bị tắt theo mặc định. Thuộc tính này chỉ hoạt động khi thuộc tính ro.test_harness
là 1
. Bạn có thể bật tính năng này 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
ps media
sẽ hiển thị hai quy trình:
- media.log
- mediaserver
Ghi lại mã nhận dạng quy trình của mediaserver
để sử dụng sau.
Hiển thị dòng thời gian
Bạn có thể yêu cầu tệp báo lỗi theo cách thủ công bất cứ lúc nào. Lệnh này hiển thị nhật ký của tất cả các dòng thời gian đang hoạt động và gần đây, sau đó xoá các nhật ký đó:
dumpsys media.log
Xin lưu ý rằng theo thiết kế, các tiến trình là độc lập và không có cơ sở để hợp nhất các tiến trình.
Khôi phục nhật ký sau khi mediaserver bị gián đoạn
Bây giờ, hãy thử loại bỏ quy trình mediaserver
: kill -9 #
, trong đó # là mã nhận dạng quy trình mà bạn đã ghi chú trước đó. Bạn sẽ thấy một tệp báo lỗi từ media.log
trong logcat
chính, cho thấy tất cả nhật ký dẫn đến sự cố.
dumpsys media.log