Welcome to mirror list, hosted at ThFree Co, Russian Federation.

OpusEncoder.java « encoder « audio « humla « lublin « se « java « main « src - gitlab.com/quite/humla.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: f46b8c4ed4296ee0c1dd3032accf6d5d2bb74bdb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/*
 * Copyright (C) 2014 Andrew Comminos
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package se.lublin.humla.audio.encoder;

import com.googlecode.javacpp.IntPointer;
import com.googlecode.javacpp.Pointer;

import java.nio.BufferOverflowException;
import java.nio.BufferUnderflowException;
import java.util.Arrays;

import se.lublin.humla.audio.javacpp.Opus;
import se.lublin.humla.exception.NativeAudioException;
import se.lublin.humla.net.PacketBuffer;

/**
* Created by andrew on 08/12/14.
*/
public class OpusEncoder implements IEncoder {
    private final byte[] mBuffer;
    private final short[] mAudioBuffer;
    private final int mFramesPerPacket;
    private final int mFrameSize;

    // Stateful
    private int mBufferedFrames;
    private int mEncodedLength;
    private boolean mTerminated;

    private Pointer mState;

    public OpusEncoder(int sampleRate, int channels, int frameSize, int framesPerPacket,
                       int bitrate, int maxBufferSize) throws NativeAudioException {
        mBuffer = new byte[maxBufferSize];
        mAudioBuffer = new short[framesPerPacket * frameSize];
        mFramesPerPacket = framesPerPacket;
        mFrameSize = frameSize;
        mBufferedFrames = 0;
        mEncodedLength = 0;
        mTerminated = false;

        IntPointer error = new IntPointer(1);
        error.put(0);
        mState = Opus.opus_encoder_create(sampleRate, channels, Opus.OPUS_APPLICATION_VOIP, error);
        if(error.get() < 0) throw new NativeAudioException("Opus encoder initialization failed with error: "+error.get());
        Opus.opus_encoder_ctl(mState, Opus.OPUS_SET_VBR_REQUEST, 0);
        Opus.opus_encoder_ctl(mState, Opus.OPUS_SET_BITRATE_REQUEST, bitrate);
    }

    @Override
    public int encode(short[] input, int inputSize) throws NativeAudioException {
        if (mBufferedFrames >= mFramesPerPacket) {
            throw new BufferOverflowException();
        }

        if (inputSize != mFrameSize) {
            throw new IllegalArgumentException("This Opus encoder implementation requires a " +
                                                       "constant frame size.");
        }

        mTerminated = false;
        System.arraycopy(input, 0, mAudioBuffer, mFrameSize * mBufferedFrames, mFrameSize);
        mBufferedFrames++;

        if (mBufferedFrames == mFramesPerPacket) {
            return encode();
        }
        return 0;
    }

    private int encode() throws NativeAudioException {
        if (mBufferedFrames < mFramesPerPacket) {
            // If encoding is done before enough frames are buffered, fill rest of packet.
            Arrays.fill(mAudioBuffer, mFrameSize * mBufferedFrames, mAudioBuffer.length, (short)0);
            mBufferedFrames = mFramesPerPacket;
        }
        int result = Opus.opus_encode(mState, mAudioBuffer, mFrameSize * mBufferedFrames,
                                             mBuffer, mBuffer.length);
        if(result < 0) throw new NativeAudioException("Opus encoding failed with error: "
                                                              + result);
        mEncodedLength = result;
        return result;
    }

    @Override
    public int getBufferedFrames() {
        return mBufferedFrames;
    }

    @Override
    public boolean isReady() {
        return mEncodedLength > 0;
    }

    @Override
    public void getEncodedData(PacketBuffer packetBuffer) throws BufferUnderflowException {
        if (!isReady()) {
            throw new BufferUnderflowException();
        }

        int size = mEncodedLength;
        if(mTerminated)
            size |= 1 << 13;
        packetBuffer.writeLong(size);
        packetBuffer.append(mBuffer, mEncodedLength);

        mBufferedFrames = 0;
        mEncodedLength = 0;
        mTerminated = false;
    }

    @Override
    public void terminate() throws NativeAudioException {
        mTerminated = true;
        if (mBufferedFrames > 0 && !isReady()) {
            // Perform encode operation on remaining audio if available.
            encode();
        }
    }

    public int getBitrate() {
        IntPointer ptr = new IntPointer(1);
        Opus.opus_encoder_ctl(mState, Opus.OPUS_GET_BITRATE_REQUEST, ptr);
        return ptr.get();
    }

    @Override
    public void destroy() {
        Opus.opus_encoder_destroy(mState);
    }
}