เครื่องมือจัดการ TV Input Framework (TIF) ทำงานร่วมกับ Audio Routing API เพื่อรองรับการเปลี่ยนแปลงเส้นทางเสียงที่ยืดหยุ่น เมื่อ System on Chip (SoC) ใช้เลเยอร์การแยกแยะฮาร์ดแวร์ของทีวี (HAL) อินพุตทีวีแต่ละรายการ (HDMI IN, จูนเนอร์ ฯลฯ) จะมี TvInputHardwareInfo ที่ระบุข้อมูล AudioPort สำหรับประเภทและที่อยู่ของเสียง
- อุปกรณ์อินพุต/เอาต์พุตเสียงจริงจะมี AudioPort ที่สอดคล้องกัน
- สตรีมเอาต์พุต/อินพุตเสียงของซอฟต์แวร์จะแสดงเป็น AudioMixPort (คลาสย่อยของ AudioPort)
จากนั้น TIF จะใช้ข้อมูล AudioPort สำหรับ API การกำหนดเส้นทางเสียง

รูปที่ 1 เฟรมเวิร์กอินพุตทีวี (TIF)
ข้อกำหนด
SoC ต้องใช้ HAL เสียงที่รองรับ API การกำหนดเส้นทางเสียงต่อไปนี้
| พอร์ตเสียง |
|
|---|---|
| อินพุตเริ่มต้น | AudioRecord (สร้างด้วยแหล่งที่มาของอินพุตเริ่มต้น) ต้องยึดแหล่งที่มาของอินพุต Null เสมือนสำหรับการรับ AUDIO_DEVICE_IN_DEFAULT ใน Android TV |
| การตรวจสอบอุปกรณ์ | ต้องรองรับอินพุต AUDIO_DEVICE_IN_LOOPBACK ซึ่งเป็นการผสมผสานเอาต์พุตเสียงทั้งหมดของทีวี (11 Khz, โมโน 16 บิต หรือ 48 Khz, โมโน 16 บิต) ใช้สำหรับการบันทึกเสียงเท่านั้น |
อุปกรณ์เสียงของทีวี
Android รองรับอุปกรณ์เสียงต่อไปนี้สำหรับอินพุต/เอาต์พุตเสียงของทีวี
system/media/audio/include/system/audio.h
หมายเหตุ: ใน Android 5.1 และเวอร์ชันก่อนหน้า เส้นทางไปยังไฟล์นี้คือ system/core/include/system/audio.h
/* output devices */ AUDIO_DEVICE_OUT_AUX_DIGITAL = 0x400, AUDIO_DEVICE_OUT_HDMI = AUDIO_DEVICE_OUT_AUX_DIGITAL, /* HDMI Audio Return Channel */ AUDIO_DEVICE_OUT_HDMI_ARC = 0x40000, /* S/PDIF out */ AUDIO_DEVICE_OUT_SPDIF = 0x80000, /* input devices */ AUDIO_DEVICE_IN_AUX_DIGITAL = AUDIO_DEVICE_BIT_IN | 0x20, AUDIO_DEVICE_IN_HDMI = AUDIO_DEVICE_IN_AUX_DIGITAL, /* TV tuner input */ AUDIO_DEVICE_IN_TV_TUNER = AUDIO_DEVICE_BIT_IN | 0x4000, /* S/PDIF in */ AUDIO_DEVICE_IN_SPDIF = AUDIO_DEVICE_BIT_IN | 0x10000, AUDIO_DEVICE_IN_LOOPBACK = AUDIO_DEVICE_BIT_IN | 0x40000,
ส่วนขยาย HAL ของเสียง
ส่วนขยาย Audio HAL สำหรับ API การกำหนดเส้นทางเสียงจะกำหนดโดยข้อมูลต่อไปนี้
system/media/audio/include/system/audio.h
หมายเหตุ: ใน Android 5.1 และเวอร์ชันก่อนหน้า เส้นทางไปยังไฟล์นี้คือ system/core/include/system/audio.h
/* audio port configuration structure used to specify a particular configuration of an audio port */
struct audio_port_config {
audio_port_handle_t id; /* port unique ID */
audio_port_role_t role; /* sink or source */
audio_port_type_t type; /* device, mix ... */
unsigned int config_mask; /* e.g. AUDIO_PORT_CONFIG_ALL */
unsigned int sample_rate; /* sampling rate in Hz */
audio_channel_mask_t channel_mask; /* channel mask if applicable */
audio_format_t format; /* format if applicable */
struct audio_gain_config gain; /* gain to apply if applicable */
union {
struct audio_port_config_device_ext device; /* device specific info */
struct audio_port_config_mix_ext mix; /* mix specific info */
struct audio_port_config_session_ext session; /* session specific info */
} ext;
};
struct audio_port {
audio_port_handle_t id; /* port unique ID */
audio_port_role_t role; /* sink or source */
audio_port_type_t type; /* device, mix ... */
unsigned int num_sample_rates; /* number of sampling rates in following array */
unsigned int sample_rates[AUDIO_PORT_MAX_SAMPLING_RATES];
unsigned int num_channel_masks; /* number of channel masks in following array */
audio_channel_mask_t channel_masks[AUDIO_PORT_MAX_CHANNEL_MASKS];
unsigned int num_formats; /* number of formats in following array */
audio_format_t formats[AUDIO_PORT_MAX_FORMATS];
unsigned int num_gains; /* number of gains in following array */
struct audio_gain gains[AUDIO_PORT_MAX_GAINS];
struct audio_port_config active_config; /* current audio port configuration */
union {
struct audio_port_device_ext device;
struct audio_port_mix_ext mix;
struct audio_port_session_ext session;
} ext;
};
hardware/libhardware/include/hardware/audio.h
struct audio_hw_device {
:
/**
* Routing control
*/
/* Creates an audio patch between several source and sink ports.
* The handle is allocated by the HAL and should be unique for this
* audio HAL module. */
int (*create_audio_patch)(struct audio_hw_device *dev,
unsigned int num_sources,
const struct audio_port_config *sources,
unsigned int num_sinks,
const struct audio_port_config *sinks,
audio_patch_handle_t *handle);
/* Release an audio patch */
int (*release_audio_patch)(struct audio_hw_device *dev,
audio_patch_handle_t handle);
/* Fills the list of supported attributes for a given audio port.
* As input, "port" contains the information (type, role, address etc...)
* needed by the HAL to identify the port.
* As output, "port" contains possible attributes (sampling rates, formats,
* channel masks, gain controllers...) for this port.
*/
int (*get_audio_port)(struct audio_hw_device *dev,
struct audio_port *port);
/* Set audio port configuration */
int (*set_audio_port_config)(struct audio_hw_device *dev,
const struct audio_port_config *config);
การทดสอบ DEVICE_IN_LOOPBACK
หากต้องการทดสอบ DEVICE_IN_LOOPBACK สําหรับการตรวจสอบทีวี ให้ใช้รหัสการทดสอบต่อไปนี้ หลังจากทำการทดสอบแล้ว ระบบจะบันทึกเสียงที่บันทึกไว้ลงใน /sdcard/record_loopback.raw ซึ่งคุณจะฟังเสียงดังกล่าวได้โดยใช้ FFmpeg
<uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
AudioRecord mRecorder;
Handler mHandler = new Handler();
int mMinBufferSize = AudioRecord.getMinBufferSize(RECORD_SAMPLING_RATE,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT);;
static final int RECORD_SAMPLING_RATE = 48000;
public void doCapture() {
mRecorder = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, RECORD_SAMPLING_RATE,
AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, mMinBufferSize * 10);
AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
ArrayList<AudioPort> audioPorts = new ArrayList<AudioPort>();
am.listAudioPorts(audioPorts);
AudioPortConfig srcPortConfig = null;
AudioPortConfig sinkPortConfig = null;
for (AudioPort audioPort : audioPorts) {
if (srcPortConfig == null
&& audioPort.role() == AudioPort.ROLE_SOURCE
&& audioPort instanceof AudioDevicePort) {
AudioDevicePort audioDevicePort = (AudioDevicePort) audioPort;
if (audioDevicePort.type() == AudioManager.DEVICE_IN_LOOPBACK) {
srcPortConfig = audioPort.buildConfig(48000, AudioFormat.CHANNEL_IN_DEFAULT,
AudioFormat.ENCODING_DEFAULT, null);
Log.d(LOG_TAG, "Found loopback audio source port : " + audioPort);
}
}
else if (sinkPortConfig == null
&& audioPort.role() == AudioPort.ROLE_SINK
&& audioPort instanceof AudioMixPort) {
sinkPortConfig = audioPort.buildConfig(48000, AudioFormat.CHANNEL_OUT_DEFAULT,
AudioFormat.ENCODING_DEFAULT, null);
Log.d(LOG_TAG, "Found recorder audio mix port : " + audioPort);
}
}
if (srcPortConfig != null && sinkPortConfig != null) {
AudioPatch[] patches = new AudioPatch[] { null };
int status = am.createAudioPatch(
patches,
new AudioPortConfig[] { srcPortConfig },
new AudioPortConfig[] { sinkPortConfig });
Log.d(LOG_TAG, "Result of createAudioPatch(): " + status);
}
mRecorder.startRecording();
processAudioData();
mRecorder.stop();
mRecorder.release();
}
private void processAudioData() {
OutputStream rawFileStream = null;
byte data[] = new byte[mMinBufferSize];
try {
rawFileStream = new BufferedOutputStream(
new FileOutputStream(new File("/sdcard/record_loopback.raw")));
} catch (FileNotFoundException e) {
Log.d(LOG_TAG, "Can't open file.", e);
}
long startTimeMs = System.currentTimeMillis();
while (System.currentTimeMillis() - startTimeMs < 5000) {
int nbytes = mRecorder.read(data, 0, mMinBufferSize);
if (nbytes <= 0) {
continue;
}
try {
rawFileStream.write(data);
} catch (IOException e) {
Log.e(LOG_TAG, "Error on writing raw file.", e);
}
}
try {
rawFileStream.close();
} catch (IOException e) {
}
Log.d(LOG_TAG, "Exit audio recording.");
}
ค้นหาไฟล์เสียงที่บันทึกไว้ใน /sdcard/record_loopback.raw และฟังโดยใช้ FFmpeg ดังนี้
adb pull /sdcard/record_loopback.rawffmpeg -f s16le -ar 48k -ac 1 -i record_loopback.raw record_loopback.wavffplay record_loopback.wav
กรณีการใช้งาน
ส่วนนี้มีกรณีการใช้งานทั่วไปสำหรับเสียงของทีวี
ทีวีจูนเนอร์ที่มีเอาต์พุตลำโพง
เมื่อตัวรับสัญญาณทีวีทำงานอยู่ API การกำหนดเส้นทางเสียงจะสร้างแพตช์เสียงระหว่างตัวรับสัญญาณกับเอาต์พุตเริ่มต้น (เช่น ลำโพง) เอาต์พุตของจูนเนอร์ไม่จำเป็นต้องถอดรหัส แต่เอาต์พุตเสียงสุดท้ายจะผสมกับ output_stream ของซอฟต์แวร์
รูปที่ 2 แพตช์เสียงสำหรับตัวรับสัญญาณทีวีที่มีเอาต์พุตลำโพง
HDMI OUT ระหว่างทีวีสด
ผู้ใช้กำลังดูรายการสดทางทีวี แล้วเปลี่ยนไปใช้เอาต์พุตเสียง HDMI (Intent.ACTION_HDMI_AUDIO_PLUG) อุปกรณ์เอาต์พุตของ output_streams ทั้งหมดจะเปลี่ยนเป็นพอร์ต HDMI_OUT และตัวจัดการ TIF จะเปลี่ยนพอร์ตซิงค์ของแพตช์เสียงจูนเนอร์ที่มีอยู่เป็นพอร์ต HDMI_OUT
รูปที่ 3 การเชื่อมต่อเสียงสำหรับ HDMI OUT จากทีวีสด