bool SndioSink::writeFrame(AudioFrame* frame) { if (!d->valid) return false; if (frame->sample_width != d->config.sample_width || frame->channels != d->config.channels ) { if (setAudioConfiguration(frame) < 0) return false; } int channels = d->config.channels; int length = frame->length; int16_t *buffer = (int16_t*)alloca(length*channels*2); int16_t** data = (int16_t**)frame->data; for (int i = 0; i < length; i++) for (int j = 0; j < channels; j++) buffer[i * channels + j] = data[j][i]; // std::cerr << "Writing frame\n"; int status = 0; do { status = ::sio_write(d->hdl, buffer, channels * length * 2); if (status == 0) { return false; } } while(false); return true; }
bool VorbisDecoder::readFrame(AudioFrame* frame) { if (!m_data->initialized) { if (!openFile()) return false; } int old_bitstream = m_data->bitstream; long v = ov_read(m_data->vf, (char*)m_data->buffer, 8192, m_data->big_endian, 2, 1, &m_data->bitstream); if (v == 0 || v == OV_EOF ) { // vorbisfile sometimes return 0 even though EOF is not yet reached if (m_data->src->eof() || m_data->src->error() || ++m_data->retries >= 16) m_data->eof = true; // std::cerr << "akode-vorbis: EOF\n"; } else if (v == OV_HOLE) { if (++m_data->retries >= 16) m_data->error = true; // std::cerr << "akode-vorbis: Hole\n"; } else if (v < 0) { m_data->error = true; // std::cerr << "akode-vorbis: Error\n"; } if (v <= 0) return false; m_data->retries = 0; if (old_bitstream != m_data->bitstream) { // changing streams, update info m_data->vi = ov_info(m_data->vf, -1); //m_data->vc = ov_comment(m_data->vf, -1); setAudioConfiguration(&m_data->config, m_data->vi); } int channels = m_data->config.channels; long length = v/(channels*2); frame->reserveSpace(&m_data->config, length); // Demux into frame int16_t* buffer = (int16_t*)m_data->buffer; int16_t** data = (int16_t**)frame->data; if (channels <= 6) { int *trans = vorbis_channel[channels]; for(int i=0; i<length; i++) for(int j=0; j<channels; j++) data [trans[j]] [i] = buffer[i*channels+j]; } else for(int i=0; i<length; i++) for(int j=0; j<channels; j++) data[j][i] = buffer[i*channels+j]; frame->pos = position(); return true; }
bool VorbisDecoder::openFile() { int status; status = ov_open_callbacks(m_data->src, m_data->vf, 0, 0, _callbacks); if (status != 0) goto fault; m_data->vi = ov_info(m_data->vf, -1); //m_data->vc = ov_comment(m_data->vf, -1); setAudioConfiguration(&m_data->config, m_data->vi); m_data->initialized = true; m_data->error = false; m_data->retries = 0; return true; fault: m_data->initialized = false; m_data->error = true; return false; }
bool FFMPEGDecoder::openFile() { d->src->openRO(); d->src->fadvise(); // The following duplicates what av_open_input_file would normally do // url_fdopen init_put_byte(&d->stream, d->file_buffer, FILE_BUFFER_SIZE, 0, d->src, akode_read, akode_write, akode_seek); d->stream.is_streamed = !d->src->seekable(); d->stream.max_packet_size = FILE_BUFFER_SIZE; { // 2048 is PROBE_BUF_SIZE from libavformat/utils.c AVProbeData pd; uint8_t buf[2048]; pd.filename = d->src->filename; pd.buf = buf; pd.buf_size = 0; pd.buf_size = get_buffer(&d->stream, buf, 2048); d->fmt = av_probe_input_format(&pd, 1); // Seek back to 0 // copied from url_fseek long offset1 = 0 - (d->stream.pos - (d->stream.buf_end - d->stream.buffer)); if (offset1 >= 0 && offset1 <= (d->stream.buf_end - d->stream.buffer)) { /* can do the seek inside the buffer */ d->stream.buf_ptr = d->stream.buffer + offset1; } else { if (!d->src->seek(0)) { d->src->close(); return false; } else { d->stream.pos = 0; d->stream.buf_ptr = d->file_buffer; d->stream.buf_end = d->file_buffer; } } } if (!d->fmt) { std::cerr << "akode: FFMPEG: Format not found\n"; closeFile(); return false; } if (av_open_input_stream(&d->ic, &d->stream, d->src->filename, d->fmt, 0) != 0) { closeFile(); return false; } av_find_stream_info( d->ic ); // Find the first a/v streams d->audioStream = -1; d->videoStream = -1; for (int i = 0; i < d->ic->nb_streams; i++) { if (d->ic->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) d->audioStream = i; else if (d->ic->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) d->videoStream = i; } if (d->audioStream == -1) { std::cerr << "akode: FFMPEG: Audio stream not found\n"; // for now require an audio stream closeFile(); return false; } // Set config if (!setAudioConfiguration(&d->config, d->ic->streams[d->audioStream]->codec)) { closeFile(); return false; } d->codec = avcodec_find_decoder(d->ic->streams[d->audioStream]->codec->codec_id); if (!d->codec) { std::cerr << "akode: FFMPEG: Codec not found\n"; closeFile(); return false; } avcodec_open( d->ic->streams[d->audioStream]->codec, d->codec ); double ffpos = (double)d->ic->streams[d->audioStream]->start_time / (double)AV_TIME_BASE; d->position = (long)(ffpos * d->config.sample_rate); return true; }
bool PlayerComponent::componentInitialize() { m_mpv = mpv::qt::Handle::FromRawHandle(mpv_create()); if (!m_mpv) throw FatalException(tr("Failed to load mpv.")); mpv_request_log_messages(m_mpv, "terminal-default"); mpv_set_option_string(m_mpv, "msg-level", "all=v"); // No mouse events mpv_set_option_string(m_mpv, "input-cursor", "no"); mpv_set_option_string(m_mpv, "cursor-autohide", "no"); mpv_set_option_string(m_mpv, "config", "yes"); mpv_set_option_string(m_mpv, "config-dir", Paths::dataDir().toUtf8().data()); // We don't need this, so avoid initializing fontconfig. mpv_set_option_string(m_mpv, "use-text-osd", "no"); // This forces the player not to rebase playback time to 0 with mkv. We // require this, because mkv transcoding lets files start at times other // than 0, and web-client expects that we return these times unchanged. mpv::qt::set_option_variant(m_mpv, "demuxer-mkv-probe-start-time", false); // Always use the system mixer. mpv_set_option_string(m_mpv, "softvol", "no"); // Just discard audio output if no audio device could be opened. This gives // us better flexibility how to react to such errors (instead of just // aborting playback immediately). mpv_set_option_string(m_mpv, "audio-fallback-to-null", "yes"); // Do not let the decoder downmix (better customization for us). mpv::qt::set_option_variant(m_mpv, "ad-lavc-downmix", false); // Make it load the hwdec interop, so hwdec can be enabled at runtime. mpv::qt::set_option_variant(m_mpv, "hwdec-preload", "auto"); // User-visible application name used by some audio APIs (at least PulseAudio). mpv_set_option_string(m_mpv, "audio-client-name", QCoreApplication::applicationName().toUtf8().data()); // User-visible stream title used by some audio APIs (at least PulseAudio and wasapi). mpv_set_option_string(m_mpv, "title", QCoreApplication::applicationName().toUtf8().data()); // Apply some low-memory settings on RPI, which is relatively memory-constrained. #ifdef TARGET_RPI // The backbuffer makes seeking back faster (without having to do a HTTP-level seek) mpv::qt::set_option_variant(m_mpv, "cache-backbuffer", 10 * 1024); // KB // The demuxer queue is used for the readahead, and also for dealing with badly // interlaved audio/video. Setting it too low increases sensitivity to network // issues, and could cause playback failure with "bad" files. mpv::qt::set_option_variant(m_mpv, "demuxer-max-bytes", 50 * 1024 * 1024); // bytes #endif mpv_observe_property(m_mpv, 0, "pause", MPV_FORMAT_FLAG); mpv_observe_property(m_mpv, 0, "cache-buffering-state", MPV_FORMAT_INT64); mpv_observe_property(m_mpv, 0, "playback-time", MPV_FORMAT_DOUBLE); mpv_observe_property(m_mpv, 0, "vo-configured", MPV_FORMAT_FLAG); mpv_observe_property(m_mpv, 0, "duration", MPV_FORMAT_DOUBLE); mpv_observe_property(m_mpv, 0, "audio-device-list", MPV_FORMAT_NODE); connect(this, &PlayerComponent::onMpvEvents, this, &PlayerComponent::handleMpvEvents, Qt::QueuedConnection); mpv_set_wakeup_callback(m_mpv, wakeup_cb, this); if (mpv_initialize(m_mpv) < 0) throw FatalException(tr("Failed to initialize mpv.")); // Setup a hook with the ID 1, which is run during the file is loaded. // Used to delay playback start for display framerate switching. // (See handler in handleMpvEvent() for details.) mpv::qt::command_variant(m_mpv, QStringList() << "hook-add" << "on_load" << "1" << "0"); updateAudioDeviceList(); setAudioConfiguration(); updateSubtitleSettings(); updateVideoSettings(); connect(SettingsComponent::Get().getSection(SETTINGS_SECTION_VIDEO), &SettingsSection::valuesUpdated, this, &PlayerComponent::updateVideoSettings); connect(SettingsComponent::Get().getSection(SETTINGS_SECTION_SUBTITLES), &SettingsSection::valuesUpdated, this, &PlayerComponent::updateSubtitleSettings); connect(SettingsComponent::Get().getSection(SETTINGS_SECTION_AUDIO), &SettingsSection::valuesUpdated, this, &PlayerComponent::setAudioConfiguration); return true; }