long SoundSourceOpus::seek(long filepos) { // In our speak, filepos is a sample in the file abstraction (i.e. it's // stereo no matter what). filepos/2 is the frame we want to seek to. if (filepos % 2 != 0) { qDebug() << "SoundSourceOpus got non-even seek target."; filepos--; } if (op_seekable(m_ptrOpusFile)) { // I can't say why filepos have to divide by two // Have no idea.. probably seek point is mono.. if (op_pcm_seek(m_ptrOpusFile, filepos / 2) != 0) { // This is totally common (i.e. you're at EOF). Let's not leave this // qDebug on. qDebug() << "opus: Seek ERR on seekable."; } // qDebug() << "Wanted:" << filepos << "GOT:" << op_pcm_tell(m_ptrOpusFile); //return op_pcm_tell(m_ptrOpusFile); // We are here allways! return filepos; } else { qDebug() << "opus: Seek ERR at file " << m_qFilename; return 0; } return filepos; }
SoundSource::OpenResult SoundSourceOpus::tryOpen(const AudioSourceConfig& /*audioSrcCfg*/) { // From opus/opusfile.h // On Windows, this string must be UTF-8 (to allow access to // files whose names cannot be represented in the current // MBCS code page). // All other systems use the native character encoding. #ifdef _WIN32 QByteArray qBAFilename = getLocalFileName().toUtf8(); #else QByteArray qBAFilename = getLocalFileName().toLocal8Bit(); #endif int errorCode = 0; DEBUG_ASSERT(!m_pOggOpusFile); m_pOggOpusFile = op_open_file(qBAFilename.constData(), &errorCode); if (!m_pOggOpusFile) { qWarning() << "Failed to open OggOpus file:" << getUrlString() << "errorCode" << errorCode; return OpenResult::FAILED; } if (!op_seekable(m_pOggOpusFile)) { qWarning() << "SoundSourceOpus:" << "Stream in" << getUrlString() << "is not seekable"; return OpenResult::UNSUPPORTED_FORMAT; } const int channelCount = op_channel_count(m_pOggOpusFile, kCurrentStreamLink); if (0 < channelCount) { setChannelCount(channelCount); } else { qWarning() << "Failed to read channel configuration of OggOpus file:" << getUrlString(); return OpenResult::FAILED; } const ogg_int64_t pcmTotal = op_pcm_total(m_pOggOpusFile, kEntireStreamLink); if (0 <= pcmTotal) { setFrameCount(pcmTotal); } else { qWarning() << "Failed to read total length of OggOpus file:" << getUrlString(); return OpenResult::FAILED; } const opus_int32 bitrate = op_bitrate(m_pOggOpusFile, kEntireStreamLink); if (0 < bitrate) { setBitrate(bitrate / 1000); } else { qWarning() << "Failed to determine bitrate of OggOpus file:" << getUrlString(); return OpenResult::FAILED; } setSamplingRate(kSamplingRate); m_curFrameIndex = getMinFrameIndex(); return OpenResult::SUCCEEDED; }
/* ================= S_OggOpus_CodecOpenStream ================= */ snd_stream_t *S_OggOpus_CodecOpenStream( const char *filename ) { snd_stream_t *stream; // Opus codec control structure OggOpusFile *of; // some variables used to get informations about the file const OpusHead *opusInfo; ogg_int64_t numSamples; // check if input is valid if ( !filename ) { return NULL; } // Open the stream stream = S_CodecUtilOpen( filename, &opus_codec ); if ( !stream ) { return NULL; } // open the codec with our callbacks and stream as the generic pointer of = op_open_callbacks( stream, &S_OggOpus_Callbacks, NULL, 0, NULL ); if ( !of ) { S_CodecUtilClose( &stream ); return NULL; } // the stream must be seekable if ( !op_seekable( of ) ) { op_free( of ); S_CodecUtilClose( &stream ); return NULL; } // get the info about channels and rate opusInfo = op_head( of, -1 ); if ( !opusInfo ) { op_free( of ); S_CodecUtilClose( &stream ); return NULL; } if ( opusInfo->stream_count != 1 ) { op_free( of ); S_CodecUtilClose( &stream ); Com_Printf( "Only Ogg Opus files with one stream are support\n" ); return NULL; } if ( opusInfo->channel_count != 1 && opusInfo->channel_count != 2 ) { op_free( of ); S_CodecUtilClose( &stream ); Com_Printf( "Only mono and stereo Ogg Opus files are supported\n" ); return NULL; } // get the number of sample-frames in the file numSamples = op_pcm_total( of, -1 ); // fill in the info-structure in the stream stream->info.rate = 48000; stream->info.width = OPUS_SAMPLEWIDTH; stream->info.channels = opusInfo->channel_count; stream->info.samples = numSamples; stream->info.size = stream->info.samples * stream->info.channels * stream->info.width; stream->info.dataofs = 0; // We use stream->pos for the file pointer in the compressed ogg file stream->pos = 0; // We use the generic pointer in stream for the opus codec control structure stream->ptr = of; return stream; }
static int opusdec_read (DB_fileinfo_t *_info, char *bytes, int size) { opusdec_info_t *info = (opusdec_info_t *)_info; // Work round some streamer limitations and infobar issue #22 if (info->new_track && is_playing_track(info->new_track)) { info->new_track = NULL; send_event(info->it, DB_EV_TRACKINFOCHANGED); info->next_update = -2; } // Don't read past the end of a sub-track int samples_to_read = size / sizeof(float) / _info->fmt.channels; int64_t endsample = deadbeef->pl_item_get_endsample (info->it); if (endsample > 0) { opus_int64 samples_left = endsample - op_pcm_tell (info->opusfile); if (samples_left < samples_to_read) { samples_to_read = (int)samples_left; size = samples_to_read * sizeof(float) * _info->fmt.channels; } } // Read until we have enough bytes to satisfy streamer, or there are none left int bytes_read = 0; int ret = OP_HOLE; int samples_read = 0; while (samples_read < samples_to_read && (ret > 0 || ret == OP_HOLE)) { int nframes = samples_to_read-samples_read; float pcm[nframes * _info->fmt.channels]; int new_link = -1; ret = op_read_float(info->opusfile, pcm, nframes * _info->fmt.channels, &new_link); if (ret < 0) { } else if (new_link != info->cur_bit_stream && !op_seekable (info->opusfile) && new_streaming_link(info, new_link)) { samples_read = samples_to_read; break; } else if (ret > 0) { for (int channel = 0; channel < _info->fmt.channels; channel++) { const float *pcm_channel = &pcm[info->channelmap ? info->channelmap[channel] : channel]; float *ptr = ((float *)bytes + samples_read*_info->fmt.channels) + channel; for (int sample = 0; sample < ret; sample ++, pcm_channel += _info->fmt.channels) { *ptr = *pcm_channel; ptr += _info->fmt.channels; } } samples_read += ret; } } bytes_read = samples_read * sizeof(float) * _info->fmt.channels; info->currentsample += bytes_read / (sizeof (float) * _info->fmt.channels); int64_t startsample = deadbeef->pl_item_get_startsample (info->it); _info->readpos = (float)(op_pcm_tell(info->opusfile) - startsample) / _info->fmt.samplerate; if (info->set_bitrate && _info->readpos > info->next_update) { const int rate = (int)op_bitrate_instant(info->opusfile) / 1000; if (rate > 0) { deadbeef->streamer_set_bitrate(rate); info->next_update = info->next_update <= 0 ? info->next_update + 1 : _info->readpos + 5; } } return bytes_read; }
void VoiceMessagesLoader::onLoad(AudioData *audio) { bool started = false; int32 audioindex = -1; Loader *l = 0; Loaders::iterator j = _loaders.end(); { QMutexLocker lock(&voicemsgsMutex); VoiceMessages *voice = audioVoice(); if (!voice) return; for (int32 i = 0; i < AudioVoiceMsgSimultaneously; ++i) { VoiceMessages::Msg &m(voice->_data[i]); if (m.audio != audio || !m.loading) continue; audioindex = i; j = _loaders.find(audio); if (j != _loaders.end() && (j.value()->fname != m.fname || j.value()->data.size() != m.data.size())) { delete j.value(); _loaders.erase(j); j = _loaders.end(); } if (j == _loaders.end()) { l = (j = _loaders.insert(audio, new Loader())).value(); l->fname = m.fname; l->data = m.data; int ret; if (m.data.isEmpty()) { l->file = op_open_file(m.fname.toUtf8().constData(), &ret); } else { l->file = op_open_memory((const unsigned char*)m.data.constData(), m.data.size(), &ret); } if (!l->file) { LOG(("Audio Error: op_open_file failed for '%1', data size '%2', error code %3").arg(m.fname).arg(m.data.size()).arg(ret)); m.state = VoiceMessageStopped; return loadError(j); } ogg_int64_t duration = op_pcm_total(l->file, -1); if (duration < 0) { LOG(("Audio Error: op_pcm_total failed to get full duration for '%1', data size '%2', error code %3").arg(m.fname).arg(m.data.size()).arg(duration)); m.state = VoiceMessageStopped; return loadError(j); } m.duration = duration; m.skipStart = 0; m.skipEnd = duration; m.position = 0; m.started = 0; started = true; } else { if (!m.skipEnd) continue; l = j.value(); } break; } } if (j == _loaders.end()) { LOG(("Audio Error: trying to load part of audio, that is not playing at the moment")); emit error(audio); return; } if (started) { l->pcm_offset = op_pcm_tell(l->file); l->pcm_print_offset = l->pcm_offset - AudioVoiceMsgFrequency; } bool finished = false; DEBUG_LOG(("Audio Info: reading buffer for file '%1', data size '%2', current pcm_offset %3").arg(l->fname).arg(l->data.size()).arg(l->pcm_offset)); QByteArray result; int64 samplesAdded = 0; while (result.size() < AudioVoiceMsgBufferSize) { opus_int16 pcm[AudioVoiceMsgFrequency * AudioVoiceMsgChannels]; int ret = op_read_stereo(l->file, pcm, sizeof(pcm) / sizeof(*pcm)); if (ret < 0) { { QMutexLocker lock(&voicemsgsMutex); VoiceMessages *voice = audioVoice(); if (voice) { VoiceMessages::Msg &m(voice->_data[audioindex]); if (m.audio == audio) { m.state = VoiceMessageStopped; } } } LOG(("Audio Error: op_read_stereo failed, error code %1").arg(ret)); return loadError(j); } int li = op_current_link(l->file); if (li != l->prev_li) { const OpusHead *head = op_head(l->file, li); const OpusTags *tags = op_tags(l->file, li); for (int32 ci = 0; ci < tags->comments; ++ci) { const char *comment = tags->user_comments[ci]; if (opus_tagncompare("METADATA_BLOCK_PICTURE", 22, comment) == 0) { OpusPictureTag pic; int err = opus_picture_tag_parse(&pic, comment); if (err >= 0) { opus_picture_tag_clear(&pic); } } } if (!op_seekable(l->file)) { l->pcm_offset = op_pcm_tell(l->file) - ret; } } if (li != l->prev_li || l->pcm_offset >= l->pcm_print_offset + AudioVoiceMsgFrequency) { l->pcm_print_offset = l->pcm_offset; } l->pcm_offset = op_pcm_tell(l->file); if (!ret) { DEBUG_LOG(("Audio Info: read completed")); finished = true; break; } result.append((const char*)pcm, sizeof(*pcm) * ret * AudioVoiceMsgChannels); l->prev_li = li; samplesAdded += ret; { QMutexLocker lock(&voicemsgsMutex); VoiceMessages *voice = audioVoice(); if (!voice) return; VoiceMessages::Msg &m(voice->_data[audioindex]); if (m.audio != audio || !m.loading || m.fname != l->fname || m.data.size() != l->data.size()) { LOG(("Audio Error: playing changed while loading")); m.state = VoiceMessageStopped; return loadError(j); } } } QMutexLocker lock(&voicemsgsMutex); VoiceMessages *voice = audioVoice(); if (!voice) return; VoiceMessages::Msg &m(voice->_data[audioindex]); if (m.audio != audio || !m.loading || m.fname != l->fname || m.data.size() != l->data.size()) { LOG(("Audio Error: playing changed while loading")); m.state = VoiceMessageStopped; return loadError(j); } if (started) { if (m.source) { alSourceStop(m.source); for (int32 i = 0; i < 3; ++i) { if (m.samplesCount[i]) { alSourceUnqueueBuffers(m.source, 1, m.buffers + i); m.samplesCount[i] = 0; } } m.nextBuffer = 0; } } if (samplesAdded) { if (!m.source) { alGenSources(1, &m.source); alSourcef(m.source, AL_PITCH, 1.f); alSourcef(m.source, AL_GAIN, 1.f); alSource3f(m.source, AL_POSITION, 0, 0, 0); alSource3f(m.source, AL_VELOCITY, 0, 0, 0); alSourcei(m.source, AL_LOOPING, 0); } if (!m.buffers[m.nextBuffer]) alGenBuffers(3, m.buffers); if (!_checkALError()) { m.state = VoiceMessageStopped; return loadError(j); } if (m.samplesCount[m.nextBuffer]) { alSourceUnqueueBuffers(m.source, 1, m.buffers + m.nextBuffer); m.skipStart += m.samplesCount[m.nextBuffer]; } m.samplesCount[m.nextBuffer] = samplesAdded; alBufferData(m.buffers[m.nextBuffer], AL_FORMAT_STEREO16, result.constData(), result.size(), AudioVoiceMsgFrequency); alSourceQueueBuffers(m.source, 1, m.buffers + m.nextBuffer); m.skipEnd -= samplesAdded; m.nextBuffer = (m.nextBuffer + 1) % 3; if (!_checkALError()) { m.state = VoiceMessageStopped; return loadError(j); } } else { finished = true; } if (finished) { m.skipEnd = 0; m.duration = m.skipStart + m.samplesCount[0] + m.samplesCount[1] + m.samplesCount[2]; } m.loading = false; if (m.state == VoiceMessageResuming || m.state == VoiceMessagePlaying || m.state == VoiceMessageStarting) { ALint state = AL_INITIAL; alGetSourcei(m.source, AL_SOURCE_STATE, &state); if (_checkALError()) { if (state != AL_PLAYING) { alSourcePlay(m.source); emit needToCheck(); } } } }
static qboolean S_OPUS_CodecOpenStream (snd_stream_t *stream) { OggOpusFile *opFile; const OpusHead *op_info; long numstreams; int res; opFile = op_open_callbacks(&stream->fh, &opc_qfs, NULL, 0, &res); if (!opFile) { Con_Printf("%s is not a valid Opus file (error %i).\n", stream->name, res); goto _fail; } stream->priv = opFile; if (!op_seekable(opFile)) { Con_Printf("Opus stream %s not seekable.\n", stream->name); goto _fail; } op_info = op_head(opFile, -1); if (!op_info) { Con_Printf("Unable to get stream information for %s.\n", stream->name); goto _fail; } /* FIXME: handle section changes */ numstreams = op_info->stream_count; if (numstreams != 1) { Con_Printf("More than one (%ld) stream in %s\n", (long)op_info->stream_count, stream->name); goto _fail; } if (op_info->channel_count != 1 && op_info->channel_count != 2) { Con_Printf("Unsupported number of channels %d in %s\n", op_info->channel_count, stream->name); goto _fail; } /* All Opus audio is coded at 48 kHz, and should also be decoded * at 48 kHz for playback: info->input_sample_rate only tells us * the sampling rate of the original input before opus encoding. * S_RawSamples() shall already downsample this, as necessary. */ stream->info.rate = 48000; stream->info.channels = op_info->channel_count; /* op_read() yields 16-bit output using native endian ordering: */ stream->info.bits = 16; stream->info.width = 2; return true; _fail: if (opFile) op_free(opFile); return false; }