123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 |
- #include "mumlib/Audio.hpp"
- #include <boost/format.hpp>
- static boost::posix_time::seconds RESET_SEQUENCE_NUMBER_INTERVAL(5);
- mumlib::Audio::Audio(int opusSampleRate, int opusEncoderBitrate)
- : logger(log4cpp::Category::getInstance("mumlib.Audio")),
- opusDecoder(nullptr),
- opusEncoder(nullptr),
- outgoingSequenceNumber(0) {
- int error;
- this->sampleRate = opusSampleRate;
- opusDecoder = opus_decoder_create(opusSampleRate, 1, &error);
- if (error != OPUS_OK) {
- throw AudioException((boost::format("failed to initialize OPUS decoder: %s") % opus_strerror(error)).str());
- }
- opusEncoder = opus_encoder_create(opusSampleRate, 1, OPUS_APPLICATION_VOIP, &error);
- if (error != OPUS_OK) {
- throw AudioException((boost::format("failed to initialize OPUS encoder: %s") % opus_strerror(error)).str());
- }
- opus_encoder_ctl(opusEncoder, OPUS_SET_VBR(0));
- setOpusEncoderBitrate(opusEncoderBitrate);
- resetEncoder();
- }
- mumlib::Audio::~Audio() {
- if (opusDecoder) {
- opus_decoder_destroy(opusDecoder);
- }
- if (opusEncoder) {
- opus_encoder_destroy(opusEncoder);
- }
- }
- void mumlib::Audio::setOpusEncoderBitrate(int bitrate) {
- int error = opus_encoder_ctl(opusEncoder, OPUS_SET_BITRATE(bitrate));
- if (error != OPUS_OK) {
- throw AudioException((boost::format("failed to initialize transmission bitrate to %d B/s: %s")
- % bitrate % opus_strerror(error)).str());
- }
- }
- int mumlib::Audio::getOpusEncoderBitrate() {
- opus_int32 bitrate;
- int error = opus_encoder_ctl(opusEncoder, OPUS_GET_BITRATE(&bitrate));
- if (error != OPUS_OK) {
- throw AudioException((boost::format("failed to read Opus bitrate: %s") % opus_strerror(error)).str());
- }
- return bitrate;
- }
- std::pair<int, bool> mumlib::Audio::decodeOpusPayload(uint8_t *inputBuffer,
- int inputLength,
- int16_t *pcmBuffer,
- int pcmBufferSize) {
- int64_t opusDataLength;
- int dataPointer = 0;
- VarInt varInt(inputBuffer);
- opusDataLength = varInt.getValue();
- dataPointer += varInt.getEncoded().size();
- bool lastPacket = (opusDataLength & 0x2000) != 0;
- opusDataLength = opusDataLength & 0x1fff;
- if (inputLength < opusDataLength + dataPointer) {
- throw AudioException((boost::format("invalid Opus payload (%d B): header %d B, expected Opus data length %d B")
- % inputLength % dataPointer % opusDataLength).str());
- }
- int outputSize = opus_decode(opusDecoder,
- reinterpret_cast<const unsigned char *>(&inputBuffer[dataPointer]),
- opusDataLength,
- pcmBuffer,
- pcmBufferSize,
- 0);
- if (outputSize <= 0) {
- throw AudioException((boost::format("failed to decode %d B of OPUS data: %s") % inputLength %
- opus_strerror(outputSize)).str());
- }
- logger.debug("%d B of Opus data decoded to %d PCM samples, last packet: %d.",
- opusDataLength, outputSize, lastPacket);
- return std::make_pair(outputSize, lastPacket);
- }
- int mumlib::Audio::encodeAudioPacket(int target, int16_t *inputPcmBuffer, int inputLength, uint8_t *outputBuffer,
- int outputBufferSize) {
- using namespace std::chrono;
- const int lastAudioPacketSentInterval = duration_cast<milliseconds>(
- system_clock::now() - lastEncodedAudioPacketTimestamp).count();
- if (lastAudioPacketSentInterval > RESET_SEQUENCE_NUMBER_INTERVAL.total_milliseconds() + 1000) {
- logger.debug("Last audio packet was sent %d ms ago, resetting encoder.", lastAudioPacketSentInterval);
- resetEncoder();
- }
- std::vector<uint8_t> header;
- header.push_back(static_cast<unsigned char &&>(0x80 | target));
- auto sequenceNumberEnc = VarInt(outgoingSequenceNumber).getEncoded();
- header.insert(header.end(), sequenceNumberEnc.begin(), sequenceNumberEnc.end());
- uint8_t tmpOpusBuffer[1024];
- const int outputSize = opus_encode(opusEncoder,
- inputPcmBuffer,
- inputLength,
- tmpOpusBuffer,
- min(outputBufferSize, 1024)
- );
- if (outputSize <= 0) {
- throw AudioException((boost::format("failed to encode %d B of PCM data: %s") % inputLength %
- opus_strerror(outputSize)).str());
- }
- auto outputSizeEnc = VarInt(outputSize).getEncoded();
- header.insert(header.end(), outputSizeEnc.begin(), outputSizeEnc.end());
- memcpy(outputBuffer, &header[0], header.size());
- memcpy(outputBuffer + header.size(), tmpOpusBuffer, (size_t) outputSize);
- int incrementNumber = 100 * inputLength / this->sampleRate;
- outgoingSequenceNumber += incrementNumber;
- lastEncodedAudioPacketTimestamp = std::chrono::system_clock::now();
- return static_cast<int>(outputSize + header.size());
- }
- void mumlib::Audio::resetEncoder() {
- int status = opus_encoder_ctl(opusEncoder, OPUS_RESET_STATE, nullptr);
- if (status != OPUS_OK) {
- throw AudioException((boost::format("failed to reset encoder: %s") % opus_strerror(status)).str());
- }
- outgoingSequenceNumber = 0;
- }
- mumlib::IncomingAudioPacket mumlib::Audio::decodeIncomingAudioPacket(uint8_t *inputBuffer, int inputBufferLength) {
- mumlib::IncomingAudioPacket incomingAudioPacket{};
- incomingAudioPacket.type = static_cast<AudioPacketType >((inputBuffer[0] & 0xE0) >> 5);
- incomingAudioPacket.target = inputBuffer[0] & 0x1F;
- std::array<int64_t *, 2> varInts = {&incomingAudioPacket.sessionId, &incomingAudioPacket.sequenceNumber};
- int dataPointer = 1;
- for (int64_t *val : varInts) {
- VarInt varInt(&inputBuffer[dataPointer]);
- *val = varInt.getValue();
- dataPointer += varInt.getEncoded().size();
- }
- incomingAudioPacket.audioPayload = &inputBuffer[dataPointer];
- incomingAudioPacket.audioPayloadLength = inputBufferLength - dataPointer;
- if (dataPointer >= inputBufferLength) {
- throw AudioException((boost::format("invalid incoming audio packet (%d B): header %d B") % inputBufferLength %
- dataPointer).str());
- }
- logger.debug(
- "Received %d B of audio packet, %d B header, %d B payload (target: %d, sessionID: %ld, seq num: %ld).",
- inputBufferLength,
- dataPointer,
- incomingAudioPacket.audioPayloadLength,
- incomingAudioPacket.target,
- incomingAudioPacket.sessionId,
- incomingAudioPacket.sequenceNumber);
- return incomingAudioPacket;
- }
|