Esempio n. 1
0
int
OpusDecoder_init(decoders_OpusDecoder *self,
                 PyObject *args, PyObject *kwds)
{
    char *filename;
    int error;

    self->opus_file = NULL;
    self->audiotools_pcm = NULL;
    self->closed = 0;

    if (!PyArg_ParseTuple(args, "s", &filename))
        return -1;

    if ((self->opus_file = op_open_file(filename, &error)) == NULL) {
        PyErr_SetString(PyExc_ValueError, "error opening Opus file");
        return -1;
    }

    self->channel_count = op_channel_count(self->opus_file, -1);

    if ((self->audiotools_pcm = open_audiotools_pcm()) == NULL)
        return -1;

    return 0;
}
Esempio n. 2
0
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;
}
Esempio n. 3
0
opus_file_t OpusDynamicLoader::open_opus_file() {
	int op_err;
	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()};
	}
	return std::move(op_file);
}
Esempio n. 4
0
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);
}
Esempio n. 5
0
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;
}
Esempio n. 6
0
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);
}
Esempio n. 7
0
/*
 * et_opus_open_file:
 * @filename: Filepath to open
 * @error: GError or %NULL
 *
 * Opens an Opus file.
 *
 * Returns: a OggOpusFile on success or %NULL on error.
 */
OggOpusFile *
et_opus_open_file (GFile *gfile, GError **error)
{
    OggOpusFile *file;
    gchar *path;
    int error_val;

    g_return_val_if_fail (error == NULL || *error == NULL, NULL);
    g_return_val_if_fail (gfile != NULL, NULL);

    path = g_file_get_path (gfile);
    /* Opusfile does the UTF-8 to UTF-16 translation on Windows
     * automatically. */
    file = op_open_file (path, &error_val);
    g_free (path);

    if (!file)
    {
        /* Got error while opening opus file */
        switch (error_val)
        {
            case OP_EREAD:
                g_set_error (error, ET_OPUS_ERROR, ET_OPUS_ERROR_READ,
                             "Error reading file");
                g_assert (error == NULL || *error != NULL);
                return NULL;

            case OP_EFAULT:
                g_set_error (error, ET_OPUS_ERROR, ET_OPUS_ERROR_FAULT,
                             "Memory allocation failure or internal library error");
                g_assert (error == NULL || *error != NULL);
                return NULL;

            case OP_EIMPL:
                g_set_error (error, ET_OPUS_ERROR, ET_OPUS_ERROR_IMPL,
                             "Stream used an unimplemented feature");
                g_assert (error == NULL || *error != NULL);
                return NULL;

            case OP_EINVAL:
                g_set_error (error, ET_OPUS_ERROR, ET_OPUS_ERROR_INVAL,
                             "seek () succeeded on this source but tell () did not");
                g_assert (error == NULL || *error != NULL);
                return NULL;

            case OP_ENOTFORMAT:
                g_set_error (error, ET_OPUS_ERROR, ET_OPUS_ERROR_NOTFORMAT,
                             "No logical stream found in a link");
                g_assert (error == NULL || *error != NULL);
                return NULL;

            case OP_EBADHEADER:
                g_set_error (error, ET_OPUS_ERROR, ET_OPUS_ERROR_BADHEADER,
                             "Corrupted header packet");
                g_assert (error == NULL || *error != NULL);
                return NULL;

            case OP_EVERSION:
                g_set_error (error, ET_OPUS_ERROR, ET_OPUS_ERROR_VERSION,
                             "ID header contained an unrecognized version number");
                g_assert (error == NULL || *error != NULL);
                return NULL;

            case OP_EBADLINK:
                g_set_error (error, ET_OPUS_ERROR, ET_OPUS_ERROR_BADLINK,
                             "Corrupted link found");
                g_assert (error == NULL || *error != NULL);
                return NULL;

            case OP_EBADTIMESTAMP:
                g_set_error (error, ET_OPUS_ERROR, ET_OPUS_ERROR_BADTIMESTAMP,
                             "First/last timestamp in a link failed checks");
                g_assert (error == NULL || *error != NULL);
                return NULL;
            default:
                g_assert_not_reached ();
                break;
        }
    }

    return file;
}
Esempio n. 8
0
/*
   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

}
Esempio n. 9
0
opus_file_t open_opus_file(const util::Path &path) {
	if (not path.is_file()) {
		throw audio::Error{
			ERR << "Audio file not found: " << path
		};
	}

	opus_file_t op_file;
	int op_err = 0;

	//asm("int $3");

	// check if the file can be opened directly
	auto native_path = path.resolve_native_path();
	if (false && native_path.size() > 0) {

		op_file.handle = {
			op_open_file(native_path.c_str(), &op_err),
			opus_deleter
		};
	}
	else {
		// open the file and move the handle to the heap
		op_file.file = std::make_unique<util::File>();
		*op_file.file = path.open_r();

		op_file.handle = {
			op_open_callbacks(op_file.file.get(), &opus_access_funcs,
			                  nullptr, 0, &op_err),
			opus_deleter
		};
	}

	if (op_err != 0) {

		const char *reason;

		switch (op_err) {
		case OP_EREAD:
			reason = "read/seek/tell failed or data has changed"; break;
		case OP_EFAULT:
			reason = "opus failed to allocate memory "
			         "or something else bad happened internally"; break;
		case OP_EIMPL:
			reason = "Stream used an unsupported feature"; break;
		case OP_EINVAL:
			reason = "seek() worked, but tell() did not, "
			         "or initial_bytes != start seek pos"; break;
		case OP_ENOTFORMAT:
			reason = "Data didn't contain opus stream"; break;
		case OP_EBADHEADER:
			reason = "Header packet was invalid or missing"; break;
		case OP_EVERSION:
			reason = "ID header has unrecognized version"; break;
		case OP_EBADLINK:
			reason = "Data we already saw before seeking not found"; break;
		case OP_EBADTIMESTAMP:
			reason = "Validity check for first/last timestamp failed"; break;

		default:
			reason = "Unknown other error in opusfile"; break;
		}

		throw audio::Error{
			ERR
			<< "Could not open opus file: "
			<< path << " = '" << native_path << "': "
			<< reason
		};
	}
	return op_file;
}
Esempio n. 10
0
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;
}
Esempio n. 11
0
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();
			}
		}
	}
}