เสียงจากทีวี

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

  • อุปกรณ์อินพุต/เอาต์พุตเสียงทางกายภาพมี AudioPort ที่เกี่ยวข้องกัน
  • เอาต์พุตเสียง/สตรีมอินพุตซอฟต์แวร์จะแสดงเป็น AudioMixPort (คลาสย่อยของ AudioPort)

จากนั้น TIF จะใช้ข้อมูล AudioPort สำหรับ Audio Routing API

เฟรมเวิร์กอินพุตของ Android TV (TIF)

รูปที่ 1 เฟรมเวิร์กอินพุตทีวี (TIF)

ข้อกำหนด

SoC ต้องใช้ HAL เสียงที่มีการรองรับ API การกำหนดเส้นทางเสียงดังต่อไปนี้

พอร์ตเสียง
  • อินพุตเสียงทีวีมีการใช้งานพอร์ตแหล่งที่มาของเสียงที่สอดคล้องกัน
  • เอาต์พุตเสียงทีวีมีการใช้งานพอร์ตซิงก์เสียงที่สอดคล้องกัน
  • สร้างแพตช์เสียงระหว่างพอร์ตเสียงอินพุตทีวีและพอร์ตเสียงเอาต์พุตของทีวีได้
อินพุตเริ่มต้น AudioRecord (สร้างขึ้นด้วยแหล่งที่มาของอินพุต DEFAULT) ต้องใช้แหล่งที่มาของอินพุตเสมือนสำหรับ การได้ผู้ใช้ใหม่ AUDIO_DEVICE_IN_DEFAULT บน Android TV
Loopback ของอุปกรณ์ ต้องรองรับอินพุต AUDIO_DEVICE_IN_LOOPBACK ที่ผสมผสานเอาต์พุตเสียงทั้งหมดอย่างสมบูรณ์ ของเอาต์พุตทีวีทั้งหมด (11 กิโลเฮิร์ตซ์, โมโน 16 บิต หรือ 48 กิโลเฮิร์ตซ์, โมโน 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 ของเสียง

ส่วนขยาย 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.raw
ffmpeg -f s16le -ar 48k -ac 1 -i record_loopback.raw record_loopback.wav
ffplay record_loopback.wav

กรณีการใช้งาน

ส่วนนี้ประกอบด้วยกรณีการใช้งานทั่วไปสำหรับเสียงทีวี

ตัวรับสัญญาณทีวีพร้อมเอาต์พุตลำโพง

เมื่อตัวรับสัญญาณทีวีทำงาน API การกำหนดเส้นทางเสียงจะสร้างแพตช์เสียงระหว่างตัวรับสัญญาณ และเอาต์พุตเริ่มต้น (เช่น ลำโพง) เอาต์พุตตัวรับสัญญาณไม่จำเป็นต้องมีการถอดรหัส แต่จำเป็นต้องถอดรหัส เอาต์พุตเสียงจะผสมกับซอฟต์แวร์output_stream

แพตช์เสียงของตัวรับสัญญาณ Android TV

รูปที่ 2 แพตช์เสียงสำหรับตัวรับสัญญาณทีวีที่มีเอาต์พุตลำโพง

HDMI OUT ระหว่างดูรายการทีวีสด

ผู้ใช้กำลังดูรายการทีวีสดแล้วเปลี่ยนไปใช้เอาต์พุตเสียง HDMI (Intent.ACTION_HDMI_AUDIO_PLUG) ที่ใช้เวลาเพียง 2 นาที อุปกรณ์เอาต์พุตของ exit_streams ทั้งหมดจะเปลี่ยนเป็นพอร์ต HDMI_OUT และตัวจัดการ TIF เปลี่ยนไป ใช้พอร์ตซิงก์ของแพตช์เสียงตัวรับสัญญาณที่มีอยู่ไปยังพอร์ต HDMI_OUT

แพตช์เสียง HDMI-OUT ของ Android TV

รูปที่ 3 แพตช์เสียงสำหรับ HDMI OUT จากรายการทีวีสด