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; }
Error AudioStreamPlaybackOpus::set_file(const String &p_file) { file=p_file; stream_valid=false; Error err; f=FileAccess::open(file,FileAccess::READ,&err); if (err) { ERR_FAIL_COND_V( err, err ); } int _err; opus_file = op_open_callbacks(f,&_op_callbacks,NULL,0,&_err); switch (_err) { case OP_EREAD: { // - Can't read the file. memdelete(f); f=NULL; ERR_FAIL_V( ERR_FILE_CANT_READ ); } break; case OP_EVERSION: // - Unrecognized version number. case OP_ENOTFORMAT: // - Stream is not Opus data. case OP_EIMPL : { // - Stream used non-implemented feature. memdelete(f); f=NULL; ERR_FAIL_V( ERR_FILE_UNRECOGNIZED ); } break; case OP_EBADLINK: // - Failed to find old data after seeking. case OP_EBADTIMESTAMP: // - Timestamp failed the validity checks. case OP_EBADHEADER: { // - Invalid or mising Opus bitstream header. memdelete(f); f=NULL; ERR_FAIL_V( ERR_FILE_CORRUPT ); } break; case OP_EFAULT: { // - Internal logic fault; indicates a bug or heap/stack corruption. memdelete(f); f=NULL; ERR_FAIL_V( ERR_BUG ); } break; } const OpusHead *oinfo = op_head(opus_file,-1); stream_channels=oinfo->channel_count; pre_skip=oinfo->pre_skip; frames_mixed=pre_skip; ogg_int64_t len = op_pcm_total(opus_file,-1); if(len < 0) { length = 0; } else { length=(len/osrate); } op_free(opus_file); memdelete(f); f=NULL; stream_valid=true; return OK; }
/* * et_opus_read_file_info: * @file: file to read info from * @ETFileInfo: ET_File_Info to put information into * @error: a GError or %NULL * * Read header information of an Opus file. * * Returns: %TRUE if successful otherwise %FALSE */ gboolean et_opus_read_file_info (GFile *gfile, ET_File_Info *ETFileInfo, GError **error) { OggOpusFile *file; const OpusHead* head; GFileInfo *info; g_return_val_if_fail (gfile != NULL && ETFileInfo != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); file = et_opus_open_file (gfile, error); if (!file) { g_assert (error == NULL || *error != NULL); return FALSE; } /* FIXME: Improve error-checking. */ head = op_head (file, -1); /* TODO: Read the vendor string from the Vorbis comment? */ ETFileInfo->version = head->version; ETFileInfo->bitrate = op_bitrate (file, -1) / 1000; ETFileInfo->mode = head->channel_count; /* All Opus audio is encoded at 48 kHz, but the input sample rate can * differ, and then input_sample_rate will be set. */ if (head->input_sample_rate != 0) { ETFileInfo->samplerate = head->input_sample_rate; } else { ETFileInfo->samplerate = 48000; } ETFileInfo->duration = op_pcm_total (file, -1) / 48000; op_free (file); info = g_file_query_info (gfile, G_FILE_ATTRIBUTE_STANDARD_SIZE, G_FILE_QUERY_INFO_NONE, NULL, NULL); if (info) { ETFileInfo->size = g_file_info_get_size (info); g_object_unref (info); } else { ETFileInfo->size = 0; } g_assert (error == NULL || *error == NULL); return TRUE; }
void cmdMusic(const std::string& args) { // terrible hack for testing audio // decode the entire file into a big buffer all at once OggOpusFile* opusFile; if (args.length() == 0) { opusFile = op_open_memory( static_cast<const uint8_t*>(EMBED_DATA(Who_Likes_to_Party_Kevin_MacLeod_incompetech_opus)), EMBED_SIZE(Who_Likes_to_Party_Kevin_MacLeod_incompetech_opus), NULL); } else { opusFile = op_open_file(args.c_str(), NULL); } if (!opusFile) { narf::console->println("Failed to open music file " + args); return; } auto newMusicSize = op_pcm_total(opusFile, -1) * 2; // stereo auto newMusicSamples = new float[newMusicSize]; size_t decoded = 0; while (decoded < newMusicSize) { auto rc = op_read_float_stereo(opusFile, newMusicSamples + decoded, newMusicSize - decoded); if (rc < 0) { narf::console->println("opusfile decode failed"); decoded = 0; break; } decoded += rc * 2; // return code is number of samples per channel, and we are decoding in stereo } if (decoded != newMusicSize) { narf::console->println("opusfile decode returned wrong number of samples (got " + std::to_string(decoded) + ", expected " + std::to_string(newMusicSize) + ")"); delete[] newMusicSamples; newMusicSamples = nullptr; newMusicSize = 0; } SDL_LockMutex(musicMutex); if (musicSamples) { delete[] musicSamples; } musicSamples = newMusicSamples; musicSize = newMusicSize; musicCursor = 0; SDL_UnlockMutex(musicMutex); op_free(opusFile); }
OpusDynamicLoader::OpusDynamicLoader(const std::string &path) : DynamicLoader{path} { // open opus file auto op_file = open_opus_file(); // read channels from the opus file channels = op_channel_count(op_file.get(), -1); length = op_pcm_total(op_file.get(), -1) * 2; log::msg("Create dynamic opus loader: len=%d, chan=%d", length, channels); }
static off_t sample_offset(OggOpusFile *opusfile, const opus_int64 sample) { if (sample <= 0 || sample == op_pcm_total(opusfile, -1)) return 0; if (op_pcm_seek(opusfile, sample)) { return -1; } return op_raw_tell(opusfile); }
Result SoundSourceOpus::open() { int error = 0; QByteArray qBAFilename = m_qFilename.toLocal8Bit(); m_ptrOpusFile = op_open_file(qBAFilename.constData(), &error); if ( m_ptrOpusFile == NULL ) { qDebug() << "opus: Input does not appear to be an Opus bitstream."; m_lFilelength = 0; return ERR; } // opusfile lib all ways gives you 48000 samplerate and stereo 16 bit sample m_iChannels = 2; this->setBitrate((int)op_bitrate_instant(m_ptrOpusFile)); this->setSampleRate(48000); this->setChannels(m_iChannels); if (m_iChannels > 2) { qDebug() << "opus: No support for more than 2 m_iChannels!"; op_free(m_ptrOpusFile); m_lFilelength = 0; return ERR; } // op_pcm_total returns the total number of frames in the ogg file. The // frame is the channel-independent measure of samples. The total samples in // the file is m_iChannels * ov_pcm_total. rryan 7/2009 I verified this by // hand. a 30 second long 48khz mono ogg and a 48khz stereo ogg both report // 1440000 for op_pcm_total. qint64 ret = op_pcm_total(m_ptrOpusFile, -1) * 2; // qDebug() << m_qFilename << "chan:" << m_iChannels << "sample:" << m_iSampleRate << "LEN:" << ret; if (ret >= 0) { // We pretend that the file is stereo to the rest of the world. m_lFilelength = ret; } else { //error if (ret == OP_EINVAL) { //The file is not seekable. Not sure if any action is needed. qDebug() << "opus: file is not seekable " << m_qFilename; } } return OK; }
int CSound::DecodeOpus(int SampleID, const void *pData, unsigned DataSize) { if(SampleID == -1 || SampleID >= NUM_SAMPLES) return -1; CSample *pSample = &m_aSamples[SampleID]; OggOpusFile *OpusFile = op_open_memory((const unsigned char *) pData, DataSize, NULL); if (OpusFile) { int NumChannels = op_channel_count(OpusFile, -1); int NumSamples = op_pcm_total(OpusFile, -1); // per channel! pSample->m_Channels = NumChannels; if(pSample->m_Channels > 2) { dbg_msg("sound/opus", "file is not mono or stereo."); return -1; } pSample->m_pData = (short *)mem_alloc(NumSamples * sizeof(short) * NumChannels, 1); int Read; int Pos = 0; while (Pos < NumSamples) { Read = op_read(OpusFile, pSample->m_pData + Pos*NumChannels, NumSamples*NumChannels, NULL); Pos += Read; } pSample->m_NumFrames = NumSamples; // ? pSample->m_Rate = 48000; pSample->m_LoopStart = -1; pSample->m_LoopEnd = -1; pSample->m_PausedAt = 0; } else { dbg_msg("sound/opus", "failed to decode sample"); return -1; } return SampleID; }
pcm_data_t OpusInMemoryLoader::get_resource() { int op_err; // open the opus file opus_file_t op_file{op_open_file(path.c_str(), &op_err), opus_deleter}; if (op_err != 0) { throw util::Error{"Could not open: %s", path.c_str()}; } // determine number of channels and number of pcm samples auto op_channels = op_channel_count(op_file.get(), -1); auto pcm_length = op_pcm_total(op_file.get(), -1); log::dbg("Opus channels=%d, pcm_length=%u", op_channels, static_cast<uint32_t>(pcm_length)); // calculate pcm buffer size depending on the number of channels // if the opus file only had one channel, the pcm buffer size must be // doubled uint32_t length = static_cast<uint32_t>(pcm_length) * 2; pcm_data_t buffer(static_cast<size_t>(length), 0); // read data from opus file int position = 0; while (true) { int samples_read = op_read(op_file.get(), &buffer.front()+position, length-position, nullptr); if (samples_read < 0) { throw util::Error{"Failed to read from opus file: errorcode=%d", samples_read}; } else if(samples_read == 0) { break; } position += samples_read * op_channels; } // convert from mono to stereo if (op_channels == 1) { for(int i = pcm_length-1; i >= 0; i--) { auto value = buffer[i]; buffer[i*2+1] = value; buffer[i*2] = value; } } return std::move(buffer); }
static gint64 xmms_opus_seek (xmms_xform_t *xform, gint64 samples, xmms_xform_seek_mode_t whence, xmms_error_t *err) { xmms_opus_data_t *data; g_return_val_if_fail (whence == XMMS_XFORM_SEEK_SET, -1); g_return_val_if_fail (xform, -1); data = xmms_xform_private_data_get (xform); g_return_val_if_fail (data, FALSE); if (samples > op_pcm_total (data->opusfile, -1)) { xmms_log_error ("Trying to seek past end of stream"); return -1; } op_pcm_seek (data->opusfile, samples); return samples; }
prMALError SDKGetInfo8( imStdParms *stdParms, imFileAccessRec8 *fileAccessInfo8, imFileInfoRec8 *SDKFileInfo8) { prMALError result = malNoError; SDKFileInfo8->hasDataRate = kPrFalse; // private data assert(SDKFileInfo8->privatedata); ImporterLocalRec8H ldataH = reinterpret_cast<ImporterLocalRec8H>(SDKFileInfo8->privatedata); stdParms->piSuites->memFuncs->lockHandle(reinterpret_cast<char**>(ldataH)); ImporterLocalRec8Ptr localRecP = reinterpret_cast<ImporterLocalRec8Ptr>( *ldataH ); SDKFileInfo8->hasVideo = kPrFalse; SDKFileInfo8->hasAudio = kPrFalse; if(localRecP) { if(localRecP->fileType == Ogg_filetype && localRecP->vf != NULL) { OggVorbis_File &vf = *localRecP->vf; vorbis_info *info = ov_info(&vf, 0); // Audio information SDKFileInfo8->hasAudio = kPrTrue; SDKFileInfo8->audInfo.numChannels = info->channels; SDKFileInfo8->audInfo.sampleRate = info->rate; SDKFileInfo8->audInfo.sampleType = kPrAudioSampleType_Compressed; SDKFileInfo8->audDuration = ov_pcm_total(&vf, 0); } else if(localRecP->fileType == Opus_filetype && localRecP->opus != NULL) { SDKFileInfo8->hasAudio = kPrTrue; SDKFileInfo8->audInfo.numChannels = op_channel_count(localRecP->opus, -1); SDKFileInfo8->audInfo.sampleRate = 48000; // Ogg Opus always uses 48 kHz SDKFileInfo8->audInfo.sampleType = kPrAudioSampleType_Compressed; SDKFileInfo8->audDuration = op_pcm_total(localRecP->opus, -1); } else if(localRecP->fileType == FLAC_filetype && localRecP->flac != NULL) { try { SDKFileInfo8->hasAudio = kPrTrue; SDKFileInfo8->audInfo.numChannels = localRecP->flac->get_channels(); SDKFileInfo8->audInfo.sampleRate = localRecP->flac->get_sample_rate(); int bitDepth = localRecP->flac->get_bits_per_sample(); SDKFileInfo8->audInfo.sampleType = bitDepth == 8 ? kPrAudioSampleType_8BitInt : bitDepth == 16 ? kPrAudioSampleType_16BitInt : bitDepth == 24 ? kPrAudioSampleType_24BitInt : bitDepth == 32 ? kPrAudioSampleType_32BitInt : bitDepth == 64 ? kPrAudioSampleType_64BitFloat : kPrAudioSampleType_Compressed; SDKFileInfo8->audDuration = localRecP->flac->get_total_samples(); } catch(...) { result = imBadFile; } } localRecP->audioSampleRate = SDKFileInfo8->audInfo.sampleRate; localRecP->numChannels = SDKFileInfo8->audInfo.numChannels; if(SDKFileInfo8->audInfo.numChannels > 2 && SDKFileInfo8->audInfo.numChannels != 6) { // Premiere can't handle anything but Mono, Stereo, and 5.1 result = imUnsupportedAudioFormat; } } stdParms->piSuites->memFuncs->unlockHandle(reinterpret_cast<char**>(ldataH)); return result; }
/* ================= 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; }
/* Parse the the file to get metadata */ Result SoundSourceOpus::parseHeader() { int error = 0; QByteArray qBAFilename = m_qFilename.toLocal8Bit(); OggOpusFile *l_ptrOpusFile = op_open_file(qBAFilename.constData(), &error); this->setBitrate((int)op_bitrate(l_ptrOpusFile, -1) / 1000); this->setSampleRate(48000); this->setChannels(2); qint64 l_lLength = op_pcm_total(l_ptrOpusFile, -1) * 2; this->setDuration(l_lLength / (48000 * 2)); this->setType("opus"); // If we don't have new enough Taglib we use libopusfile parser! #if (TAGLIB_MAJOR_VERSION >= 1) && (TAGLIB_MINOR_VERSION >= 9) TagLib::Ogg::Opus::File f(qBAFilename.constData()); // Takes care of all the default metadata bool result = processTaglibFile(f); TagLib::Ogg::XiphComment *tag = f.tag(); if (tag) { processXiphComment(tag); } #else // From Taglib 1.9.x Opus is supported // Before that we have parse tags by this code int i = 0; const OpusTags *l_ptrOpusTags = op_tags(l_ptrOpusFile, -1); // This is left for debug reasons !! // qDebug() << "opus: We have " << l_ptrOpusTags->comments; for( i = 0; i < l_ptrOpusTags->comments; i ++){ QString l_SWholeTag = QString(l_ptrOpusTags->user_comments[i]); QString l_STag = l_SWholeTag.left(l_SWholeTag.indexOf("=")); QString l_SPayload = l_SWholeTag.right((l_SWholeTag.length() - l_SWholeTag.indexOf("=")) - 1); if (!l_STag.compare("ARTIST") ) { this->setArtist(l_SPayload); } else if (!l_STag.compare("ALBUM")) { this->setAlbum(l_SPayload); } else if (!l_STag.compare("BPM")) { this->setBPM(l_SPayload.toFloat()); } else if (!l_STag.compare("YEAR") || !l_STag.compare("DATE")) { this->setYear(l_SPayload); } else if (!l_STag.compare("GENRE")) { this->setGenre(l_SPayload); } else if (!l_STag.compare("TRACKNUMBER")) { this->setTrackNumber(l_SPayload); } else if (!l_STag.compare("COMPOSER")) { this->setComposer(l_SPayload); } else if (!l_STag.compare("ALBUMARTIST")) { this->setAlbumArtist(l_SPayload); } else if (!l_STag.compare("TITLE")) { this->setTitle(l_SPayload); } else if (!l_STag.compare("REPLAYGAIN_TRACK_PEAK")) { } else if (!l_STag.compare("REPLAYGAIN_TRACK_GAIN")) { this->parseReplayGainString (l_SPayload); } else if (!l_STag.compare("REPLAYGAIN_ALBUM_PEAK")) { } else if (!l_STag.compare("REPLAYGAIN_ALBUM_GAIN")) { } // This is left fot debug reasons!! //qDebug() << "Comment" << i << l_ptrOpusTags->comment_lengths[i] << //" (" << l_ptrOpusTags->user_comments[i] << ")" << l_STag << "*" << l_SPayload; } op_free(l_ptrOpusFile); return OK; #endif #if TAGLIB_MAJOR_VERSION >= 1 && TAGLIB_MINOR_VERSION >= 9 return result ? OK : ERR; #endif }
static int opusdec_init (DB_fileinfo_t *_info, DB_playItem_t *it) { opusdec_info_t *info = (opusdec_info_t *)_info; if (!info->info.file) { deadbeef->pl_lock (); const char *uri = strdupa (deadbeef->pl_find_meta (it, ":URI")); deadbeef->pl_unlock (); DB_FILE *fp = deadbeef->fopen (uri); if (!fp) { return -1; } info->info.file = fp; info->it = it; deadbeef->pl_item_ref (it); } info->opusfile = opus_file_open (info->info.file); if (!info->opusfile) { return -1; } const OpusHead *head = op_head (info->opusfile, 0); if (head->channel_count > 8) { trace ("opus: the track has %d channels, but 8 is max supported.\n"); return -1; } // take this parameters from your input file // we set constants for clarity sake _info->fmt.bps = 32; _info->fmt.is_float = 1; _info->fmt.channels = head->channel_count; _info->fmt.samplerate = 48000; if (head->mapping_family == 1) { info->channelmap = oggedit_vorbis_channel_map (head->channel_count); } for (int i = 0; i < (_info->fmt.channels&0x1f); i++) { _info->fmt.channelmask |= 1 << i; } _info->readpos = 0; _info->plugin = &plugin; // set all gain adjustment to 0, because deadbeef is performing that. op_set_gain_offset (info->opusfile, OP_ABSOLUTE_GAIN, 0); if (info->info.file->vfs->is_streaming ()) { deadbeef->pl_item_set_startsample (it, 0); if (deadbeef->pl_get_item_duration (it) < 0) { deadbeef->pl_item_set_endsample (it, -1); } else { deadbeef->pl_item_set_endsample (it, op_pcm_total (info->opusfile, -1) - 1); } if (update_vorbis_comments (it, info->opusfile, -1)) return -1; deadbeef->pl_set_meta_int(it, ":TRACKNUM", 0); } else { opusdec_seek_sample (_info, 0); } deadbeef->pl_replace_meta (it, "!FILETYPE", "Ogg Opus"); deadbeef->pl_set_meta_int (it, ":CHANNELS", head->channel_count); info->cur_bit_stream = -1; return 0; }
int64 OggOpusReader::FrameCount(void) { return(op_pcm_total(pvfile, -1)); }
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 gboolean xmms_opus_init (xmms_xform_t *xform) { xmms_opus_data_t *data; gint ret; guint playtime; const gchar *metakey; g_return_val_if_fail (xform, FALSE); data = g_new0 (xmms_opus_data_t, 1), data->callbacks.read = opus_callback_read; data->callbacks.close = opus_callback_close; data->callbacks.tell = opus_callback_tell; data->callbacks.seek = opus_callback_seek; data->current = -1; xmms_xform_private_data_set (xform, data); data->opusfile = op_open_callbacks (xform, &data->callbacks, NULL, 0, &ret); if (ret) { return FALSE; } playtime = op_pcm_total (data->opusfile, -1) / 48000; if (playtime != OP_EINVAL) { gint filesize; metakey = XMMS_MEDIALIB_ENTRY_PROPERTY_SIZE; if (xmms_xform_metadata_get_int (xform, metakey, &filesize)) { xmms_opus_set_duration (xform, playtime); } } xmms_opus_read_metadata (xform, data); /* xmms_xform_outdata_type_add (xform, XMMS_STREAM_TYPE_MIMETYPE, "audio/pcm", XMMS_STREAM_TYPE_FMT_FORMAT, XMMS_SAMPLE_FORMAT_FLOAT, XMMS_STREAM_TYPE_FMT_CHANNELS, data->channels, XMMS_STREAM_TYPE_FMT_SAMPLERATE, 48000, XMMS_STREAM_TYPE_END); */ xmms_xform_outdata_type_add (xform, XMMS_STREAM_TYPE_MIMETYPE, "audio/pcm", XMMS_STREAM_TYPE_FMT_FORMAT, XMMS_SAMPLE_FORMAT_S16, XMMS_STREAM_TYPE_FMT_CHANNELS, data->channels, XMMS_STREAM_TYPE_FMT_SAMPLERATE, 48000, XMMS_STREAM_TYPE_END); return TRUE; }
Result SoundSourceOpus::parseTrackMetadataAndCoverArt( TrackMetadata* pTrackMetadata, QImage* pCoverArt) const { if (OK == SoundSource::parseTrackMetadataAndCoverArt( pTrackMetadata, pCoverArt)) { // Done if the default implementation in the base class // supports Opus files. return OK; } // Beginning with version 1.9.0 TagLib supports the Opus format. // Until this becomes the minimum version required by Mixxx tags // in .opus files must also be parsed using opusfile. The following // code should removed as soon as it is no longer needed! // // NOTE(uklotzde): The following code has been found in SoundSourceOpus // and will not be improved. We are aware of its shortcomings like // the lack of proper error handling. // 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 error = 0; OggOpusFileOwner l_ptrOpusFile( op_open_file(qBAFilename.constData(), &error)); int i = 0; const OpusTags *l_ptrOpusTags = op_tags(l_ptrOpusFile, -1); pTrackMetadata->setChannels(op_channel_count(l_ptrOpusFile, -1)); pTrackMetadata->setSampleRate(Mixxx::SoundSourceOpus::kSamplingRate); pTrackMetadata->setBitrate(op_bitrate(l_ptrOpusFile, -1) / 1000); pTrackMetadata->setDuration( op_pcm_total(l_ptrOpusFile, -1) / pTrackMetadata->getSampleRate()); bool hasDate = false; for (i = 0; i < l_ptrOpusTags->comments; ++i) { QString l_SWholeTag = QString(l_ptrOpusTags->user_comments[i]); QString l_STag = l_SWholeTag.left(l_SWholeTag.indexOf("=")); QString l_SPayload = l_SWholeTag.right((l_SWholeTag.length() - l_SWholeTag.indexOf("=")) - 1); if (!l_STag.compare("ARTIST")) { pTrackMetadata->setArtist(l_SPayload); } else if (!l_STag.compare("ALBUM")) { pTrackMetadata->setAlbum(l_SPayload); } else if (!l_STag.compare("BPM")) { pTrackMetadata->setBpm(l_SPayload.toFloat()); } else if (!l_STag.compare("DATE")) { // Prefer "DATE" over "YEAR" pTrackMetadata->setYear(l_SPayload.trimmed()); // Avoid to overwrite "DATE" with "YEAR" hasDate |= !pTrackMetadata->getYear().isEmpty(); } else if (!hasDate && !l_STag.compare("YEAR")) { pTrackMetadata->setYear(l_SPayload.trimmed()); } else if (!l_STag.compare("GENRE")) { pTrackMetadata->setGenre(l_SPayload); } else if (!l_STag.compare("TRACKNUMBER")) { pTrackMetadata->setTrackNumber(l_SPayload); } else if (!l_STag.compare("COMPOSER")) { pTrackMetadata->setComposer(l_SPayload); } else if (!l_STag.compare("ALBUMARTIST")) { pTrackMetadata->setAlbumArtist(l_SPayload); } else if (!l_STag.compare("TITLE")) { pTrackMetadata->setTitle(l_SPayload); } else if (!l_STag.compare("REPLAYGAIN_TRACK_GAIN")) { bool trackGainRatioValid = false; double trackGainRatio = ReplayGain::parseGain2Ratio(l_SPayload, &trackGainRatioValid); if (trackGainRatioValid) { ReplayGain trackGain(pTrackMetadata->getReplayGain()); trackGain.setRatio(trackGainRatio); pTrackMetadata->setReplayGain(trackGain); } } // This is left fot debug reasons!! //qDebug() << "Comment" << i << l_ptrOpusTags->comment_lengths[i] << //" (" << l_ptrOpusTags->user_comments[i] << ")" << l_STag << "*" << l_SPayload; } return OK; }
static DB_playItem_t * opusdec_insert (ddb_playlist_t *plt, DB_playItem_t *after, const char *fname) { DB_FILE *fp = deadbeef->fopen (fname); if (!fp) { return NULL; } int64_t fsize = deadbeef->fgetlength (fp); if (fp->vfs->is_streaming ()) { DB_playItem_t *it = deadbeef->pl_item_alloc_init (fname, plugin.plugin.id); deadbeef->plt_set_item_duration (plt, it, -1); deadbeef->pl_add_meta (it, "title", NULL); after = deadbeef->plt_insert_item (plt, after, it); deadbeef->pl_item_unref (it); deadbeef->fclose (fp); return after; } OggOpusFile *opusfile = opus_file_open (fp); if (!opusfile) { deadbeef->fclose (fp); return NULL; } long nstreams = op_link_count (opusfile); int64_t currentsample = 0; for (int stream = 0; stream < nstreams; stream++) { const OpusHead *head = op_head (opusfile, stream); if (!head) { continue; } int64_t totalsamples = op_pcm_total (opusfile, stream); const float duration = totalsamples / 48000.f; DB_playItem_t *it = deadbeef->pl_item_alloc_init (fname, plugin.plugin.id); deadbeef->pl_set_meta_int (it, ":TRACKNUM", stream); deadbeef->plt_set_item_duration (plt, it, duration); if (nstreams > 1) { deadbeef->pl_item_set_startsample (it, currentsample); deadbeef->pl_item_set_endsample (it, currentsample + totalsamples - 1); deadbeef->pl_set_item_flags (it, DDB_IS_SUBTRACK); } if (update_vorbis_comments (it, opusfile, stream)) continue; int samplerate = 48000; int64_t startsample = deadbeef->pl_item_get_startsample (it); int64_t endsample = deadbeef->pl_item_get_endsample (it); const off_t start_offset = sample_offset(opusfile, startsample-1); const off_t end_offset = sample_offset(opusfile, endsample); char *filetype = NULL; const off_t stream_size = oggedit_opus_stream_info(deadbeef->fopen(fname), start_offset, end_offset, &filetype); if (filetype) { deadbeef->pl_replace_meta(it, ":FILETYPE", filetype); free(filetype); } if (stream_size > 0) { set_meta_ll(it, ":OPUS_STREAM_SIZE", stream_size); deadbeef->pl_set_meta_int(it, ":BITRATE", 8.f * samplerate * stream_size / totalsamples / 1000); } set_meta_ll (it, ":FILE_SIZE", fsize); deadbeef->pl_set_meta_int (it, ":CHANNELS", head->channel_count); deadbeef->pl_set_meta_int (it, ":SAMPLERATE", samplerate); if (nstreams == 1) { DB_playItem_t *cue = deadbeef->plt_process_cue (plt, after, it, totalsamples, samplerate); if (cue) { deadbeef->pl_item_unref (it); op_free(opusfile); deadbeef->fclose (fp); return cue; } } else { currentsample += totalsamples; } after = deadbeef->plt_insert_item (plt, after, it); deadbeef->pl_item_unref (it); } op_free(opusfile); deadbeef->fclose (fp); return after; }