void ELD::show() { int i; if (!isValid()) { VBAUDIO("Invalid ELD"); return; } VBAUDIO(QString("Detected monitor %1 at connection type %2") .arg(product_name().simplified()) .arg(connection_name())); if (m_e.spk_alloc) { VBAUDIO(QString("available speakers:%1") .arg(channel_allocation_desc())); } VBAUDIO(QString("max LPCM channels = %1").arg(maxLPCMChannels())); VBAUDIO(QString("max channels = %1").arg(maxChannels())); VBAUDIO(QString("supported codecs = %1").arg(codecs_desc())); for (i = 0; i < m_e.sad_count; i++) { VBAUDIO(sad_desc(i)); } }
void AudioOutputPulseAudio::ServerInfoCallback( pa_context *context, const pa_server_info *inf, void *arg) { QString fn_log_tag = "ServerInfoCallback, "; VBAUDIO(fn_log_tag + QString("PulseAudio server info - host name: %1, server version: " "%2, server name: %3, default sink: %4") .arg(inf->host_name).arg(inf->server_version) .arg(inf->server_name).arg(inf->default_sink_name)); }
char *AudioOutputPulseAudio::ChooseHost(void) { QString fn_log_tag = "ChooseHost, "; char *pulse_host = NULL; char *device = strdup(main_device.toAscii().constData()); const char *host; for (host=device; host && *host != ':' && *host != 0; host++); if (host && *host != 0) host++; if ( !(!host || *host == 0 || strcmp(host,"default") == 0)) { if ((pulse_host = new char[strlen(host)])) strcpy(pulse_host, host); else VBERROR(fn_log_tag + QString("allocation of pulse host '%1' char[%2] failed") .arg(host).arg(strlen(host) + 1)); } if (!pulse_host && strcmp(host,"default") != 0) { char *env_pulse_host = getenv("PULSE_SERVER"); if (env_pulse_host && (*env_pulse_host != '\0')) { int host_len = strlen(env_pulse_host) + 1; if ((pulse_host = new char[host_len])) strcpy(pulse_host, env_pulse_host); else { VBERROR(fn_log_tag + QString("allocation of pulse host '%1' char[%2] failed") .arg(env_pulse_host).arg(host_len)); } } } VBAUDIO(fn_log_tag + QString("chosen PulseAudio server: %1") .arg((pulse_host != NULL) ? pulse_host : "default")); free(device); return pulse_host; }
bool AudioOutputPulseAudio::ConnectPlaybackStream(void) { QString fn_log_tag = "ConnectPlaybackStream, "; pstream = pa_stream_new(pcontext, "MythTV playback", &sample_spec, &channel_map); if (!pstream) { VBERROR(fn_log_tag + QString("failed to create new playback stream")); return false; } pa_stream_set_state_callback(pstream, StreamStateCallback, this); pa_stream_set_write_callback(pstream, WriteCallback, this); pa_stream_set_overflow_callback(pstream, BufferFlowCallback, (char*)"over"); pa_stream_set_underflow_callback(pstream, BufferFlowCallback, (char*)"under"); if (set_initial_vol) { int volume = gCoreContext->GetNumSetting("MasterMixerVolume", 80); pa_cvolume_set(&volume_control, channels, (float)volume * (float)PA_VOLUME_NORM / 100.0f); } else pa_cvolume_reset(&volume_control, channels); fragment_size = (samplerate * 25 * output_bytes_per_frame) / 1000; buffer_settings.maxlength = (uint32_t)-1; buffer_settings.tlength = fragment_size * 4; buffer_settings.prebuf = (uint32_t)-1; buffer_settings.minreq = (uint32_t)-1; int flags = PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_NO_REMIX_CHANNELS; pa_stream_connect_playback(pstream, NULL, &buffer_settings, (pa_stream_flags_t)flags, &volume_control, NULL); pa_context_state_t cstate; pa_stream_state_t sstate; bool connected = false, failed = false; while (!(connected || failed)) { switch (cstate = pa_context_get_state(pcontext)) { case PA_CONTEXT_FAILED: case PA_CONTEXT_TERMINATED: VERBOSE(VB_IMPORTANT, LOC_ERR + fn_log_tag + QString("context is stuffed, %1") .arg(pa_strerror(pa_context_errno( pcontext)))); failed = true; break; default: switch (sstate = pa_stream_get_state(pstream)) { case PA_STREAM_READY: connected = true; break; case PA_STREAM_FAILED: case PA_STREAM_TERMINATED: VBERROR(fn_log_tag + QString("stream failed or was terminated, " "context state %1, stream state %2") .arg(cstate).arg(sstate)); failed = true; break; default: pa_threaded_mainloop_wait(mainloop); break; } } } const pa_buffer_attr *buf_attr = pa_stream_get_buffer_attr(pstream); fragment_size = buf_attr->tlength >> 2; soundcard_buffer_size = buf_attr->maxlength; VBAUDIO(fn_log_tag + QString("fragment size %1, soundcard buffer size %2") .arg(fragment_size).arg(soundcard_buffer_size)); return (connected && !failed); }
bool AudioOutputPulseAudio::ContextConnect(void) { QString fn_log_tag = "ContextConnect, "; if (pcontext) { VBERROR(fn_log_tag + "context appears to exist, but shouldn't (yet)"); pa_context_unref(pcontext); pcontext = NULL; return false; } pcontext = pa_context_new(pa_threaded_mainloop_get_api(mainloop), "MythTV"); if (!pcontext) { VBERROR(fn_log_tag + "failed to acquire new context"); return false; } pa_context_set_state_callback(pcontext, ContextStateCallback, this); char *pulse_host = ChooseHost(); int chk = pa_context_connect( pcontext, pulse_host, (pa_context_flags_t)0, NULL); delete(pulse_host); if (chk < 0) { VBERROR(fn_log_tag + QString("context connect failed: %1") .arg(pa_strerror(pa_context_errno(pcontext)))); return false; } bool connected = false; pa_context_state_t state = pa_context_get_state(pcontext); for (; !connected; state = pa_context_get_state(pcontext)) { switch(state) { case PA_CONTEXT_READY: VBAUDIO(fn_log_tag +"context connection ready"); connected = true; continue; case PA_CONTEXT_FAILED: case PA_CONTEXT_TERMINATED: VBERROR(fn_log_tag + QString("context connection failed or terminated: %1") .arg(pa_strerror(pa_context_errno(pcontext)))); return false; default: VBAUDIO(fn_log_tag + "waiting for context connection ready"); pa_threaded_mainloop_wait(mainloop); break; } } pa_operation *op = pa_context_get_server_info(pcontext, ServerInfoCallback, this); if (op) pa_operation_unref(op); else VBERROR(fn_log_tag + "failed to get PulseAudio server info"); return true; }
bool AudioOutputPulseAudio::OpenDevice() { QString fn_log_tag = "OpenDevice, "; if (channels > PULSE_MAX_CHANNELS ) { VBERROR(fn_log_tag + QString("audio channel limit %1, but %2 requested") .arg(PULSE_MAX_CHANNELS).arg(channels)); return false; } sample_spec.rate = samplerate; sample_spec.channels = volume_control.channels = channels; switch (output_format) { case FORMAT_U8: sample_spec.format = PA_SAMPLE_U8; break; case FORMAT_S16: sample_spec.format = PA_SAMPLE_S16NE; break; // define from PA 0.9.15 only #ifdef PA_MAJOR case FORMAT_S24LSB: sample_spec.format = PA_SAMPLE_S24_32NE; break; #endif case FORMAT_S32: sample_spec.format = PA_SAMPLE_S32NE; break; case FORMAT_FLT: sample_spec.format = PA_SAMPLE_FLOAT32NE; break; break; default: VBERROR(fn_log_tag + QString("unsupported sample format %1") .arg(output_format)); return false; } if (!pa_sample_spec_valid(&sample_spec)) { VBERROR(fn_log_tag + "invalid sample spec"); return false; } else { char spec[PA_SAMPLE_SPEC_SNPRINT_MAX]; pa_sample_spec_snprint(spec, sizeof(spec), &sample_spec); VBAUDIO(fn_log_tag + QString("using sample spec %1").arg(spec)); } pa_channel_map *pmap = NULL; if(!(pmap = pa_channel_map_init_auto(&channel_map, channels, PA_CHANNEL_MAP_WAVEEX)) < 0) { VBERROR(fn_log_tag + "failed to init channel map"); return false; } channel_map = *pmap; mainloop = pa_threaded_mainloop_new(); if (!mainloop) { VBERROR(fn_log_tag + "failed to get new threaded mainloop"); return false; } pa_threaded_mainloop_start(mainloop); pa_threaded_mainloop_lock(mainloop); if (!ContextConnect()) { pa_threaded_mainloop_unlock(mainloop); pa_threaded_mainloop_stop(mainloop); return false; } if (!ConnectPlaybackStream()) { pa_threaded_mainloop_unlock(mainloop); pa_threaded_mainloop_stop(mainloop); return false; } pa_threaded_mainloop_unlock(mainloop); return true; }
int ELD::update_eld(const char *buf, int size) { int mnl; int i; m_e.eld_ver = GRAB_BITS(buf, 0, 3, 5); if (m_e.eld_ver != ELD_VER_CEA_861D && m_e.eld_ver != ELD_VER_PARTIAL) { VBAUDIO(QString("Unknown ELD version %1").arg(m_e.eld_ver)); goto out_fail; } m_e.eld_size = size; m_e.baseline_len = GRAB_BITS(buf, 2, 0, 8); mnl = GRAB_BITS(buf, 4, 0, 5); m_e.cea_edid_ver = GRAB_BITS(buf, 4, 5, 3); m_e.support_hdcp = GRAB_BITS(buf, 5, 0, 1); m_e.support_ai = GRAB_BITS(buf, 5, 1, 1); m_e.conn_type = GRAB_BITS(buf, 5, 2, 2); m_e.sad_count = GRAB_BITS(buf, 5, 4, 4); m_e.aud_synch_delay = GRAB_BITS(buf, 6, 0, 8) * 2; m_e.spk_alloc = GRAB_BITS(buf, 7, 0, 7); m_e.port_id = LE_INT64(buf + 8); /* not specified, but the spec's tendency is little endian */ m_e.manufacture_id = LE_SHORT(buf + 16); m_e.product_id = LE_SHORT(buf + 18); if (mnl > ELD_MAX_MNL) { VBAUDIO(QString("MNL is reserved value %1").arg(mnl)); goto out_fail; } else if (ELD_FIXED_BYTES + mnl > size) { VBAUDIO(QString("out of range MNL %1").arg(mnl)); goto out_fail; } else { strncpy(m_e.monitor_name, (char *)buf + ELD_FIXED_BYTES, mnl + 1); m_e.monitor_name[mnl] = '\0'; } for (i = 0; i < m_e.sad_count; i++) { if (ELD_FIXED_BYTES + mnl + 3 * (i + 1) > size) { VBAUDIO(QString("out of range SAD %1").arg(i)); goto out_fail; } update_sad(i, buf + ELD_FIXED_BYTES + mnl + 3 * i); } /* * Assume the highest speakers configuration */ if (!m_e.spk_alloc) m_e.spk_alloc = 0xffff; m_e.eld_valid = true; return 0; out_fail: m_e.eld_valid = false; return -1; }
void ELD::update_sad(int index, const char *buf) { int val; cea_sad *a = m_e.sad + index; val = GRAB_BITS(buf, 1, 0, 7); a->rates = 0; for (int i = 0; i < 7; i++) if ((val & (1 << i)) != 0) a->rates |= cea_sampling_frequencies[i + 1]; a->channels = GRAB_BITS(buf, 0, 0, 3); a->channels++; a->sample_bits = 0; a->max_bitrate = 0; a->format = GRAB_BITS(buf, 0, 3, 4); m_e.formats |= 1 << a->format; switch (a->format) { case TYPE_REF_STREAM_HEADER: VBAUDIO("audio coding type 0 not expected"); break; case TYPE_LPCM: a->sample_bits = GRAB_BITS(buf, 2, 0, 3); break; case TYPE_AC3: case TYPE_MPEG1: case TYPE_MP3: case TYPE_MPEG2: case TYPE_AACLC: case TYPE_DTS: case TYPE_ATRAC: a->max_bitrate = GRAB_BITS(buf, 2, 0, 8); a->max_bitrate *= 8000; break; case TYPE_SACD: break; case TYPE_EAC3: break; case TYPE_DTS_HD: break; case TYPE_MLP: break; case TYPE_DST: break; case TYPE_WMAPRO: a->profile = GRAB_BITS(buf, 2, 0, 3); break; case TYPE_REF_CXT: a->format = GRAB_BITS(buf, 2, 3, 5); if (a->format == XTYPE_HE_REF_CT || a->format >= XTYPE_FIRST_RESERVED) { VBAUDIO(QString("audio coding xtype %1 not expected") .arg(a->format)); a->format = 0; } else { a->format += TYPE_HE_AAC - XTYPE_HE_AAC; } break; } }