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 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
-
cd frameworks/av/services/audioflinger
- Chỉnh sửa
Configuration.h
- Bỏ ghi chú
#define TEE_SINK
. - Xây dựng lại
libaudioflinger.so
. -
adb root
-
adb remount
- Đẩ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
-
adb shell getprop | grep ro.debuggable
Xác nhận rằng đầu ra là:[ro.debuggable]: [1]
-
adb shell
-
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
-
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 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
- Chạy thử nghiệm âm thanh của bạn.
-
adb shell dumpsys media.audio_flinger
- 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. - 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 radumpsys
nhưng vẫn được lưu vào/data/misc/audioserver
khi đóng bản nhạc. - 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:
- Hoàn nguyên các thay đổi mã nguồn thành
Configuration.h
. - Xây dựng lại
libaudioflinger.so
. - Đẩy hoặc đồng bộ hóa
libaudioflinger.so
đã khôi phục với/system/lib
của thiết bị. -
adb shell
-
rm /data/local.prop
-
rm /data/misc/audioserver/*.wav
-
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ư
FastMixer
vàFastCapture
. - 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 của nó.
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:

Hình 1. Kiến trúc trước media.log
Điểm đáng chú ý:
-
init
forks và execsmediaserver
. -
init
phát hiện cái chết củamediaserver
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:

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ế sao cho 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 FastMixer
và FastCapture
.
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 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 sử dụng bởi các luồng FastMixer
và FastCapture
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 FastMixer
và FastCapture
, 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_harness
là 1
. 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 shellLệ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