avcodec/vorbisenc: Use a bufqueue in encoding with smaller lengths
authorTyler Jones <tdjones879@gmail.com>
Tue, 30 May 2017 17:28:16 +0000 (11:28 -0600)
committerRostislav Pehlivanov <atomnuker@gmail.com>
Mon, 5 Jun 2017 15:35:07 +0000 (16:35 +0100)
Switching the vorbis encoder to use a buffer queue for input frames allows
saving lookahead samples more easily and safely for psychoacoustic systems,
requiring less pointer arithmetic in the case of transient windows.

libavcodec/vorbisenc.c

index f196d79..489fede 100644 (file)
@@ -1044,20 +1044,117 @@ static int apply_window_and_mdct(vorbis_enc_context *venc,
     return 1;
 }
 
+/* Used for padding the last encoded packet */
+static AVFrame *spawn_empty_frame(AVCodecContext *avctx, int channels)
+{
+    AVFrame *f = av_frame_alloc();
+
+    if (!f)
+        return NULL;
+
+    f->format = avctx->sample_fmt;
+    f->nb_samples = avctx->frame_size;
+    f->channel_layout = avctx->channel_layout;
+
+    if (av_frame_get_buffer(f, 4)) {
+        av_frame_free(&f);
+        return NULL;
+    }
+
+    for (int ch = 0; ch < channels; ch++) {
+        size_t bps = av_get_bytes_per_sample(f->format);
+        memset(f->extended_data[ch], 0, bps * f->nb_samples);
+    }
+    return f;
+}
+
+static float **alloc_audio_arrays(int channels, int frame_size)
+{
+    float **audio = av_mallocz_array(channels, sizeof(float *));
+    if (!audio)
+        return NULL;
+
+    for (int ch = 0; ch < channels; ch++) {
+        audio[ch] = av_mallocz_array(frame_size, sizeof(float));
+        if (!audio[ch]) {
+            // alloc has failed, free everything allocated thus far
+            for (ch--; ch >= 0; ch--)
+                av_free(audio[ch]);
+            av_free(audio);
+            return NULL;
+        }
+    }
+
+    return audio;
+}
+
+/* Concatenate audio frames into an appropriately sized array of samples */
+static void move_audio(vorbis_enc_context *venc, float **audio, int *samples, int sf_size)
+{
+    AVFrame *cur = NULL;
+    int frame_size = 1 << (venc->log2_blocksize[1] - 1);
+    int subframes = frame_size / sf_size;
+
+    for (int sf = 0; sf < subframes; sf++) {
+        cur = ff_bufqueue_get(&venc->bufqueue);
+        *samples += cur->nb_samples;
+
+        for (int ch = 0; ch < venc->channels; ch++) {
+            const float *input = (float *) cur->extended_data[ch];
+            const size_t len  = cur->nb_samples * sizeof(float);
+            memcpy(&audio[ch][sf*sf_size], input, len);
+        }
+        av_frame_free(&cur);
+    }
+}
+
 static int vorbis_encode_frame(AVCodecContext *avctx, AVPacket *avpkt,
                                const AVFrame *frame, int *got_packet_ptr)
 {
     vorbis_enc_context *venc = avctx->priv_data;
-    float **audio = frame ? (float **)frame->extended_data : NULL;
-    int samples = frame ? frame->nb_samples : 0;
+    float **audio = NULL;
+    int i, ret, need_more;
+    int samples = 0, frame_size = 1 << (venc->log2_blocksize[1] - 1);
     vorbis_enc_mode *mode;
     vorbis_enc_mapping *mapping;
     PutBitContext pb;
-    int i, ret;
+
+    if (frame) {
+        if ((ret = ff_af_queue_add(&venc->afq, frame)) < 0)
+            return ret;
+        ff_bufqueue_add(avctx, &venc->bufqueue, av_frame_clone(frame));
+    } else
+        if (!venc->afq.remaining_samples)
+            return 0;
+
+    need_more = venc->bufqueue.available * avctx->frame_size < frame_size;
+    need_more = frame && need_more;
+    if (need_more)
+        return 0;
+
+    audio = alloc_audio_arrays(venc->channels, frame_size);
+    if (!audio)
+        return AVERROR(ENOMEM);
+
+    /* Pad the bufqueue with empty frames for encoding the last packet. */
+    if (!frame) {
+        if (venc->bufqueue.available * avctx->frame_size < frame_size) {
+            int frames_needed = (frame_size/avctx->frame_size) - venc->bufqueue.available;
+
+            for (int i = 0; i < frames_needed; i++) {
+               AVFrame *empty = spawn_empty_frame(avctx, venc->channels);
+               if (!empty)
+                   return AVERROR(ENOMEM);
+
+               ff_bufqueue_add(avctx, &venc->bufqueue, empty);
+            }
+        }
+    }
+
+    move_audio(venc, audio, &samples, avctx->frame_size);
 
     if (!apply_window_and_mdct(venc, audio, samples))
         return 0;
-    samples = 1 << (venc->log2_blocksize[0] - 1);
 
     if ((ret = ff_alloc_packet2(avctx, avpkt, 8192, 0)) < 0)
         return ret;
@@ -1116,16 +1213,11 @@ static int vorbis_encode_frame(AVCodecContext *avctx, AVPacket *avpkt,
     flush_put_bits(&pb);
     avpkt->size = put_bits_count(&pb) >> 3;
 
-    avpkt->duration = ff_samples_to_time_base(avctx, avctx->frame_size);
-    if (frame) {
-        if (frame->pts != AV_NOPTS_VALUE)
-            avpkt->pts = ff_samples_to_time_base(avctx, frame->pts);
-    } else {
-        avpkt->pts = venc->next_pts;
-    }
-    if (avpkt->pts != AV_NOPTS_VALUE)
-        venc->next_pts = avpkt->pts + avpkt->duration;
+    for (int ch = 0; ch < venc->channels; ch++)
+        av_free(audio[ch]);
+    av_free(audio);
 
+    ff_af_queue_remove(&venc->afq, frame_size, &avpkt->pts, &avpkt->duration);
     *got_packet_ptr = 1;
     return 0;
 }
@@ -1217,7 +1309,7 @@ static av_cold int vorbis_encode_init(AVCodecContext *avctx)
         goto error;
     avctx->extradata_size = ret;
 
-    avctx->frame_size = 1 << (venc->log2_blocksize[0] - 1);
+    avctx->frame_size = 64;
 
     ff_af_queue_init(avctx, &venc->afq);