gstflacenc.c 46 KB
Newer Older
Andy Wingo's avatar
Andy Wingo committed
1
/* GStreamer
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
 * SECTION:element-flacenc
 * @see_also: #GstFlacDec
 *
 * flacenc encodes FLAC streams.
 * <ulink url="http://flac.sourceforge.net/">FLAC</ulink>
 * is a Free Lossless Audio Codec.
 *
 * <refsect2>
 * <title>Example launch line</title>
 * |[
 * gst-launch audiotestsrc num-buffers=100 ! flacenc ! filesink location=beep.flac
 * ]|
 * </refsect2>
 */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
34

35
36
37
/* TODO: - We currently don't handle discontinuities in the stream in a useful
 *         way and instead rely on the developer plugging in audiorate if
 *         the stream contains discontinuities.
38
 */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
39

40
41
42
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
43
44
45
46
#include <stdlib.h>
#include <string.h>

#include <gstflacenc.h>
47
#include <gst/audio/audio.h>
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
48
#include <gst/tag/tag.h>
49
#include <gst/gsttagsetter.h>
50

51
52
/* Taken from http://flac.sourceforge.net/format.html#frame_header */
static const GstAudioChannelPosition channel_positions[8][8] = {
53
  {GST_AUDIO_CHANNEL_POSITION_MONO},
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
  {GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
      GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT}, {
        GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
        GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
      GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER}, {
        GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
        GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
        GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
      GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT}, {
        GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
        GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
        GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
        GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
      GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT}, {
        GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
        GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
        GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
71
        GST_AUDIO_CHANNEL_POSITION_LFE1,
72
73
74
75
76
77
78
79
80
        GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
      GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT},
  /* FIXME: 7/8 channel layouts are not defined in the FLAC specs */
  {
        GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
        GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
        GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
        GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT,
        GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
81
        GST_AUDIO_CHANNEL_POSITION_LFE1,
82
83
84
85
86
87
      GST_AUDIO_CHANNEL_POSITION_REAR_CENTER}, {
        GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
        GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
        GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
        GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT,
        GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
88
        GST_AUDIO_CHANNEL_POSITION_LFE1,
89
90
91
        GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT,
      GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT}
};
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
92

93
94
95
96
97
98
99
100
101
102
103
104
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
#define FORMATS "{ S8LE, S16LE, S24LE, S32LE } "
#else
#define FORMATS "{ S8BE, S16BE, S24BE, S32BE } "
#endif

#define FLAC_SINK_CAPS                                    \
    "audio/x-raw, "                                       \
    "format = (string) " FORMATS ", "                     \
    "layout = (string) interleaved, "                     \
    "rate = (int) [ 1, 655350 ], "                        \
    "channels = (int) [ 1, 8 ]"
105
106
107
108
109
110
111
112
113
114

static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("audio/x-flac")
    );

static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
115
    GST_STATIC_CAPS (FLAC_SINK_CAPS)
116
117
    );

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
118
119
enum
{
120
121
122
123
124
125
126
127
128
129
130
131
132
  PROP_0,
  PROP_QUALITY,
  PROP_STREAMABLE_SUBSET,
  PROP_MID_SIDE_STEREO,
  PROP_LOOSE_MID_SIDE_STEREO,
  PROP_BLOCKSIZE,
  PROP_MAX_LPC_ORDER,
  PROP_QLP_COEFF_PRECISION,
  PROP_QLP_COEFF_PREC_SEARCH,
  PROP_ESCAPE_CODING,
  PROP_EXHAUSTIVE_MODEL_SEARCH,
  PROP_MIN_RESIDUAL_PARTITION_ORDER,
  PROP_MAX_RESIDUAL_PARTITION_ORDER,
133
  PROP_RICE_PARAMETER_SEARCH_DIST,
134
135
  PROP_PADDING,
  PROP_SEEKPOINTS
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
136
137
};

138
139
140
GST_DEBUG_CATEGORY_STATIC (flacenc_debug);
#define GST_CAT_DEFAULT flacenc_debug

Wim Taymans's avatar
Wim Taymans committed
141
#define gst_flac_enc_parent_class parent_class
142
G_DEFINE_TYPE_WITH_CODE (GstFlacEnc, gst_flac_enc, GST_TYPE_AUDIO_ENCODER,
Wim Taymans's avatar
Wim Taymans committed
143
    G_IMPLEMENT_INTERFACE (GST_TYPE_TAG_SETTER, NULL));
144

145
146
147
148
149
150
static gboolean gst_flac_enc_start (GstAudioEncoder * enc);
static gboolean gst_flac_enc_stop (GstAudioEncoder * enc);
static gboolean gst_flac_enc_set_format (GstAudioEncoder * enc,
    GstAudioInfo * info);
static GstFlowReturn gst_flac_enc_handle_frame (GstAudioEncoder * enc,
    GstBuffer * in_buf);
Wim Taymans's avatar
Wim Taymans committed
151
static GstCaps *gst_flac_enc_getcaps (GstAudioEncoder * enc, GstCaps * filter);
152
153
static gboolean gst_flac_enc_sink_event (GstAudioEncoder * enc,
    GstEvent * event);
154

155
static void gst_flac_enc_finalize (GObject * object);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
156

157
158
159
static gboolean gst_flac_enc_update_quality (GstFlacEnc * flacenc,
    gint quality);
static void gst_flac_enc_set_property (GObject * object, guint prop_id,
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
160
    const GValue * value, GParamSpec * pspec);
161
static void gst_flac_enc_get_property (GObject * object, guint prop_id,
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
162
163
    GValue * value, GParamSpec * pspec);

164
165
166
167
168
169
170
171
172
173
static FLAC__StreamEncoderWriteStatus
gst_flac_enc_write_callback (const FLAC__StreamEncoder * encoder,
    const FLAC__byte buffer[], size_t bytes,
    unsigned samples, unsigned current_frame, void *client_data);
static FLAC__StreamEncoderSeekStatus
gst_flac_enc_seek_callback (const FLAC__StreamEncoder * encoder,
    FLAC__uint64 absolute_byte_offset, void *client_data);
static FLAC__StreamEncoderTellStatus
gst_flac_enc_tell_callback (const FLAC__StreamEncoder * encoder,
    FLAC__uint64 * absolute_byte_offset, void *client_data);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
174

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
175
176
177
178
179
180
181
182
183
184
185
186
187
typedef struct
{
  gboolean exhaustive_model_search;
  gboolean escape_coding;
  gboolean mid_side;
  gboolean loose_mid_side;
  guint qlp_coeff_precision;
  gboolean qlp_coeff_prec_search;
  guint min_residual_partition_order;
  guint max_residual_partition_order;
  guint rice_parameter_search_dist;
  guint max_lpc_order;
  guint blocksize;
188
}
189
GstFlacEncParams;
Wim Taymans's avatar
Wim Taymans committed
190

191
static const GstFlacEncParams flacenc_params[] = {
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
192
193
194
195
196
197
198
199
200
201
  {FALSE, FALSE, FALSE, FALSE, 0, FALSE, 2, 2, 0, 0, 1152},
  {FALSE, FALSE, TRUE, TRUE, 0, FALSE, 2, 2, 0, 0, 1152},
  {FALSE, FALSE, TRUE, FALSE, 0, FALSE, 0, 3, 0, 0, 1152},
  {FALSE, FALSE, FALSE, FALSE, 0, FALSE, 3, 3, 0, 6, 4608},
  {FALSE, FALSE, TRUE, TRUE, 0, FALSE, 3, 3, 0, 8, 4608},
  {FALSE, FALSE, TRUE, FALSE, 0, FALSE, 3, 3, 0, 8, 4608},
  {FALSE, FALSE, TRUE, FALSE, 0, FALSE, 0, 4, 0, 8, 4608},
  {TRUE, FALSE, TRUE, FALSE, 0, FALSE, 0, 6, 0, 8, 4608},
  {TRUE, FALSE, TRUE, FALSE, 0, FALSE, 0, 6, 0, 12, 4608},
  {TRUE, TRUE, TRUE, FALSE, 0, FALSE, 0, 16, 0, 32, 4608},
Wim Taymans's avatar
Wim Taymans committed
202
203
204
};

#define DEFAULT_QUALITY 5
205
#define DEFAULT_PADDING 0
206
#define DEFAULT_SEEKPOINTS 0
Wim Taymans's avatar
Wim Taymans committed
207

208
#define GST_TYPE_FLAC_ENC_QUALITY (gst_flac_enc_quality_get_type ())
209
static GType
210
gst_flac_enc_quality_get_type (void)
Wim Taymans's avatar
Wim Taymans committed
211
212
{
  static GType qtype = 0;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
213

Wim Taymans's avatar
Wim Taymans committed
214
215
  if (qtype == 0) {
    static const GEnumValue values[] = {
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
216
      {0, "0 - Fastest compression", "0"},
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
217
218
219
220
      {1, "1", "1"},
      {2, "2", "2"},
      {3, "3", "3"},
      {4, "4", "4"},
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
221
      {5, "5 - Default", "5"},
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
222
223
      {6, "6", "6"},
      {7, "7", "7"},
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
224
225
      {8, "8 - Highest compression", "8"},
      {9, "9 - Insane", "9"},
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
226
      {0, NULL, NULL}
Wim Taymans's avatar
Wim Taymans committed
227
    };
228

229
    qtype = g_enum_register_static ("GstFlacEncQuality", values);
Wim Taymans's avatar
Wim Taymans committed
230
231
232
233
  }
  return qtype;
}

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
234
static void
235
gst_flac_enc_class_init (GstFlacEncClass * klass)
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
236
237
238
{
  GObjectClass *gobject_class;
  GstElementClass *gstelement_class;
239
  GstAudioEncoderClass *base_class;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
240

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
241
242
  gobject_class = (GObjectClass *) klass;
  gstelement_class = (GstElementClass *) klass;
243
  base_class = (GstAudioEncoderClass *) (klass);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
244

Wim Taymans's avatar
Wim Taymans committed
245
246
247
  GST_DEBUG_CATEGORY_INIT (flacenc_debug, "flacenc", 0,
      "Flac encoding element");

248
249
250
  gobject_class->set_property = gst_flac_enc_set_property;
  gobject_class->get_property = gst_flac_enc_get_property;
  gobject_class->finalize = gst_flac_enc_finalize;
Wim Taymans's avatar
Wim Taymans committed
251

252
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_QUALITY,
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
253
      g_param_spec_enum ("quality",
254
255
          "Quality",
          "Speed versus compression tradeoff",
256
          GST_TYPE_FLAC_ENC_QUALITY, DEFAULT_QUALITY,
257
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
258
  g_object_class_install_property (G_OBJECT_CLASS (klass),
259
      PROP_STREAMABLE_SUBSET, g_param_spec_boolean ("streamable-subset",
260
261
          "Streamable subset",
          "true to limit encoder to generating a Subset stream, else false",
262
263
          TRUE,
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
264
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_MID_SIDE_STEREO,
265
      g_param_spec_boolean ("mid-side-stereo", "Do mid side stereo",
266
          "Do mid side stereo (only for stereo input)",
267
          flacenc_params[DEFAULT_QUALITY].mid_side,
268
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
269
  g_object_class_install_property (G_OBJECT_CLASS (klass),
270
      PROP_LOOSE_MID_SIDE_STEREO, g_param_spec_boolean ("loose-mid-side-stereo",
271
          "Loose mid side stereo", "Loose mid side stereo",
272
          flacenc_params[DEFAULT_QUALITY].loose_mid_side,
273
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
274
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_BLOCKSIZE,
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
275
      g_param_spec_uint ("blocksize", "Blocksize", "Blocksize in samples",
276
          FLAC__MIN_BLOCK_SIZE, FLAC__MAX_BLOCK_SIZE,
277
          flacenc_params[DEFAULT_QUALITY].blocksize,
278
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
279
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_MAX_LPC_ORDER,
280
      g_param_spec_uint ("max-lpc-order", "Max LPC order",
281
282
          "Max LPC order; 0 => use only fixed predictors", 0,
          FLAC__MAX_LPC_ORDER, flacenc_params[DEFAULT_QUALITY].max_lpc_order,
283
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
284
  g_object_class_install_property (G_OBJECT_CLASS (klass),
285
      PROP_QLP_COEFF_PRECISION, g_param_spec_uint ("qlp-coeff-precision",
286
287
288
          "QLP coefficients precision",
          "Precision in bits of quantized linear-predictor coefficients; 0 = automatic",
          0, 32, flacenc_params[DEFAULT_QUALITY].qlp_coeff_precision,
289
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
290
  g_object_class_install_property (G_OBJECT_CLASS (klass),
291
      PROP_QLP_COEFF_PREC_SEARCH, g_param_spec_boolean ("qlp-coeff-prec-search",
292
293
294
295
          "Do QLP coefficients precision search",
          "false = use qlp_coeff_precision, "
          "true = search around qlp_coeff_precision, take best",
          flacenc_params[DEFAULT_QUALITY].qlp_coeff_prec_search,
296
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
297
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_ESCAPE_CODING,
298
      g_param_spec_boolean ("escape-coding", "Do Escape coding",
299
300
          "search for escape codes in the entropy coding stage "
          "for slightly better compression",
301
          flacenc_params[DEFAULT_QUALITY].escape_coding,
302
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
303
  g_object_class_install_property (G_OBJECT_CLASS (klass),
304
      PROP_EXHAUSTIVE_MODEL_SEARCH,
305
      g_param_spec_boolean ("exhaustive-model-search",
306
307
308
          "Do exhaustive model search",
          "do exhaustive search of LP coefficient quantization (expensive!)",
          flacenc_params[DEFAULT_QUALITY].exhaustive_model_search,
309
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
310
  g_object_class_install_property (G_OBJECT_CLASS (klass),
311
      PROP_MIN_RESIDUAL_PARTITION_ORDER,
312
      g_param_spec_uint ("min-residual-partition-order",
313
314
315
          "Min residual partition order",
          "Min residual partition order (above 4 doesn't usually help much)", 0,
          16, flacenc_params[DEFAULT_QUALITY].min_residual_partition_order,
316
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
317
  g_object_class_install_property (G_OBJECT_CLASS (klass),
318
      PROP_MAX_RESIDUAL_PARTITION_ORDER,
319
      g_param_spec_uint ("max-residual-partition-order",
320
321
322
          "Max residual partition order",
          "Max residual partition order (above 4 doesn't usually help much)", 0,
          16, flacenc_params[DEFAULT_QUALITY].max_residual_partition_order,
323
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
324
  g_object_class_install_property (G_OBJECT_CLASS (klass),
325
      PROP_RICE_PARAMETER_SEARCH_DIST,
326
      g_param_spec_uint ("rice-parameter-search-dist",
327
328
329
330
          "rice_parameter_search_dist",
          "0 = try only calc'd parameter k; else try all [k-dist..k+dist] "
          "parameters, use best", 0, FLAC__MAX_RICE_PARTITION_ORDER,
          flacenc_params[DEFAULT_QUALITY].rice_parameter_search_dist,
331
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
Wim Taymans's avatar
Wim Taymans committed
332

333
334
335
336
337
338
339
340
341
342
343
344
  /**
   * GstFlacEnc:padding
   *
   * Write a PADDING block with this length in bytes
   *
   * Since: 0.10.16
   **/
  g_object_class_install_property (G_OBJECT_CLASS (klass),
      PROP_PADDING,
      g_param_spec_uint ("padding",
          "Padding",
          "Write a PADDING block with this length in bytes", 0, G_MAXUINT,
345
346
          DEFAULT_PADDING,
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
347

348
  /**
349
   * GstFlacEnc:seekpoints
350
   *
351
352
   * Write a SEEKTABLE block with a specific number of seekpoints
   * or with a specific interval spacing.
353
354
355
356
357
358
359
360
361
   *
   * Since: 0.10.18
   **/
  g_object_class_install_property (G_OBJECT_CLASS (klass),
      PROP_SEEKPOINTS,
      g_param_spec_int ("seekpoints",
          "Seekpoints",
          "Add SEEKTABLE metadata (if > 0, number of entries, if < 0, interval in sec)",
          -G_MAXINT, G_MAXINT,
362
363
          DEFAULT_SEEKPOINTS,
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
364

Wim Taymans's avatar
Wim Taymans committed
365
366
367
368
369
370
371
372
373
  gst_element_class_add_pad_template (gstelement_class,
      gst_static_pad_template_get (&src_factory));
  gst_element_class_add_pad_template (gstelement_class,
      gst_static_pad_template_get (&sink_factory));

  gst_element_class_set_details_simple (gstelement_class, "FLAC audio encoder",
      "Codec/Encoder/Audio",
      "Encodes audio with the FLAC lossless audio encoder",
      "Wim Taymans <wim.taymans@chello.be>");
Wim Taymans's avatar
Wim Taymans committed
374
375
376
377
378
379
380

  base_class->start = GST_DEBUG_FUNCPTR (gst_flac_enc_start);
  base_class->stop = GST_DEBUG_FUNCPTR (gst_flac_enc_stop);
  base_class->set_format = GST_DEBUG_FUNCPTR (gst_flac_enc_set_format);
  base_class->handle_frame = GST_DEBUG_FUNCPTR (gst_flac_enc_handle_frame);
  base_class->getcaps = GST_DEBUG_FUNCPTR (gst_flac_enc_getcaps);
  base_class->event = GST_DEBUG_FUNCPTR (gst_flac_enc_sink_event);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
381
382
}

Wim Taymans's avatar
Wim Taymans committed
383
static void
Wim Taymans's avatar
Wim Taymans committed
384
gst_flac_enc_init (GstFlacEnc * flacenc)
Wim Taymans's avatar
Wim Taymans committed
385
{
386
  GstAudioEncoder *enc = GST_AUDIO_ENCODER (flacenc);
Wim Taymans's avatar
Wim Taymans committed
387

388
  flacenc->encoder = FLAC__stream_encoder_new ();
389
  gst_flac_enc_update_quality (flacenc, DEFAULT_QUALITY);
390
391
392
393

  /* arrange granulepos marking (and required perfect ts) */
  gst_audio_encoder_set_mark_granule (enc, TRUE);
  gst_audio_encoder_set_perfect_timestamp (enc, TRUE);
Wim Taymans's avatar
Wim Taymans committed
394
395
396
}

static void
397
gst_flac_enc_finalize (GObject * object)
Wim Taymans's avatar
Wim Taymans committed
398
{
399
  GstFlacEnc *flacenc = GST_FLAC_ENC (object);
Wim Taymans's avatar
Wim Taymans committed
400

401
  FLAC__stream_encoder_delete (flacenc->encoder);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
402

403
  G_OBJECT_CLASS (parent_class)->finalize (object);
Wim Taymans's avatar
Wim Taymans committed
404
405
}

406
407
408
409
410
411
412
413
414
415
416
static gboolean
gst_flac_enc_start (GstAudioEncoder * enc)
{
  GstFlacEnc *flacenc = GST_FLAC_ENC (enc);

  GST_DEBUG_OBJECT (enc, "start");
  flacenc->stopped = TRUE;
  flacenc->got_headers = FALSE;
  flacenc->last_flow = GST_FLOW_OK;
  flacenc->offset = 0;
  flacenc->eos = FALSE;
417
  flacenc->tags = gst_tag_list_new_empty ();
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450

  return TRUE;
}

static gboolean
gst_flac_enc_stop (GstAudioEncoder * enc)
{
  GstFlacEnc *flacenc = GST_FLAC_ENC (enc);

  GST_DEBUG_OBJECT (enc, "stop");
  gst_tag_list_free (flacenc->tags);
  flacenc->tags = NULL;
  if (FLAC__stream_encoder_get_state (flacenc->encoder) !=
      FLAC__STREAM_ENCODER_UNINITIALIZED) {
    flacenc->stopped = TRUE;
    FLAC__stream_encoder_finish (flacenc->encoder);
  }
  if (flacenc->meta) {
    FLAC__metadata_object_delete (flacenc->meta[0]);

    if (flacenc->meta[1])
      FLAC__metadata_object_delete (flacenc->meta[1]);

    if (flacenc->meta[2])
      FLAC__metadata_object_delete (flacenc->meta[2]);

    g_free (flacenc->meta);
    flacenc->meta = NULL;
  }
  g_list_foreach (flacenc->headers, (GFunc) gst_mini_object_unref, NULL);
  g_list_free (flacenc->headers);
  flacenc->headers = NULL;

451
452
  gst_tag_setter_reset_tags (GST_TAG_SETTER (enc));

453
454
455
  return TRUE;
}

456
457
458
459
460
static void
add_one_tag (const GstTagList * list, const gchar * tag, gpointer user_data)
{
  GList *comments;
  GList *it;
461
  GstFlacEnc *flacenc = GST_FLAC_ENC (user_data);
462

463
464
465
  /* IMAGE and PREVIEW_IMAGE tags are already written
   * differently, no need to store them inside the
   * vorbiscomments too */
466
467
  if (strcmp (tag, GST_TAG_IMAGE) == 0
      || strcmp (tag, GST_TAG_PREVIEW_IMAGE) == 0)
468
469
    return;

470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
  comments = gst_tag_to_vorbis_comments (list, tag);
  for (it = comments; it != NULL; it = it->next) {
    FLAC__StreamMetadata_VorbisComment_Entry commment_entry;

    commment_entry.length = strlen (it->data);
    commment_entry.entry = it->data;
    FLAC__metadata_object_vorbiscomment_insert_comment (flacenc->meta[0],
        flacenc->meta[0]->data.vorbis_comment.num_comments,
        commment_entry, TRUE);
    g_free (it->data);
  }
  g_list_free (comments);
}

static void
485
gst_flac_enc_set_metadata (GstFlacEnc * flacenc, guint64 total_samples)
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
486
{
487
488
  const GstTagList *user_tags;
  GstTagList *copy;
489
  gint entries = 1;
490
  gint n_images, n_preview_images;
491
492
  GstAudioInfo *info =
      gst_audio_encoder_get_audio_info (GST_AUDIO_ENCODER (flacenc));
493
494

  g_return_if_fail (flacenc != NULL);
495
  user_tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (flacenc));
496
497
498
499
  if ((flacenc->tags == NULL) && (user_tags == NULL)) {
    return;
  }
  copy = gst_tag_list_merge (user_tags, flacenc->tags,
500
      gst_tag_setter_get_tag_merge_mode (GST_TAG_SETTER (flacenc)));
501
502
503
504
505
  n_images = gst_tag_list_get_tag_size (copy, GST_TAG_IMAGE);
  n_preview_images = gst_tag_list_get_tag_size (copy, GST_TAG_PREVIEW_IMAGE);

  flacenc->meta =
      g_new0 (FLAC__StreamMetadata *, 3 + n_images + n_preview_images);
506
507
508
509
510

  flacenc->meta[0] =
      FLAC__metadata_object_new (FLAC__METADATA_TYPE_VORBIS_COMMENT);
  gst_tag_list_foreach (copy, add_one_tag, flacenc);

511
512
  if (n_images + n_preview_images > 0) {
    GstBuffer *buffer;
Wim Taymans's avatar
Wim Taymans committed
513
#if 0
514
515
516
    GstCaps *caps;
    GstStructure *structure;
    GstTagImageType image_type = GST_TAG_IMAGE_TYPE_NONE;
Wim Taymans's avatar
Wim Taymans committed
517
#endif
518
    gint i;
Wim Taymans's avatar
Wim Taymans committed
519
    GstMapInfo map;
520
521
522
523
524
525
526
527
528
529
530
531
532
533

    for (i = 0; i < n_images + n_preview_images; i++) {
      if (i < n_images) {
        if (!gst_tag_list_get_buffer_index (copy, GST_TAG_IMAGE, i, &buffer))
          continue;
      } else {
        if (!gst_tag_list_get_buffer_index (copy, GST_TAG_PREVIEW_IMAGE,
                i - n_images, &buffer))
          continue;
      }

      flacenc->meta[entries] =
          FLAC__metadata_object_new (FLAC__METADATA_TYPE_PICTURE);

Wim Taymans's avatar
Wim Taymans committed
534
#if 0
535
536
537
538
539
540
541
542
543
544
      caps = gst_buffer_get_caps (buffer);
      structure = gst_caps_get_structure (caps, 0);

      gst_structure_get (structure, "image-type", GST_TYPE_TAG_IMAGE_TYPE,
          &image_type, NULL);
      /* Convert to ID3v2 APIC image type */
      if (image_type == GST_TAG_IMAGE_TYPE_NONE)
        image_type = (i < n_images) ? 0x00 : 0x01;
      else
        image_type = image_type + 2;
Wim Taymans's avatar
Wim Taymans committed
545
#endif
546

Wim Taymans's avatar
Wim Taymans committed
547
      gst_buffer_map (buffer, &map, GST_MAP_READ);
548
      FLAC__metadata_object_picture_set_data (flacenc->meta[entries],
Wim Taymans's avatar
Wim Taymans committed
549
550
          map.data, map.size, TRUE);
      gst_buffer_unmap (buffer, &map);
Wim Taymans's avatar
Wim Taymans committed
551

Wim Taymans's avatar
Wim Taymans committed
552
#if 0
553
554
555
556
557
      /* FIXME: There's no way to set the picture type in libFLAC */
      flacenc->meta[entries]->data.picture.type = image_type;
      FLAC__metadata_object_picture_set_mime_type (flacenc->meta[entries],
          (char *) gst_structure_get_name (structure), TRUE);
      gst_caps_unref (caps);
Wim Taymans's avatar
Wim Taymans committed
558
559
#endif

560
561
562
563
564
      gst_buffer_unref (buffer);
      entries++;
    }
  }

565
566
567
568
  if (flacenc->seekpoints && total_samples != GST_CLOCK_TIME_NONE) {
    gboolean res;
    guint samples;

569
    flacenc->meta[entries] =
570
571
572
573
        FLAC__metadata_object_new (FLAC__METADATA_TYPE_SEEKTABLE);
    if (flacenc->seekpoints > 0) {
      res =
          FLAC__metadata_object_seektable_template_append_spaced_points
574
          (flacenc->meta[entries], flacenc->seekpoints, total_samples);
575
    } else {
576
      samples = -flacenc->seekpoints * GST_AUDIO_INFO_RATE (info);
577
578
      res =
          FLAC__metadata_object_seektable_template_append_spaced_points_by_samples
579
          (flacenc->meta[entries], samples, total_samples);
580
581
582
583
584
    }
    if (!res) {
      GST_DEBUG_OBJECT (flacenc, "adding seekpoint template %d failed",
          flacenc->seekpoints);
      FLAC__metadata_object_delete (flacenc->meta[1]);
585
      flacenc->meta[entries] = NULL;
586
587
588
589
590
591
592
    } else {
      entries++;
    }
  } else if (flacenc->seekpoints && total_samples == GST_CLOCK_TIME_NONE) {
    GST_WARNING_OBJECT (flacenc, "total time unknown; can not add seekpoints");
  }

593
  if (flacenc->padding > 0) {
594
595
596
597
    flacenc->meta[entries] =
        FLAC__metadata_object_new (FLAC__METADATA_TYPE_PADDING);
    flacenc->meta[entries]->length = flacenc->padding;
    entries++;
598
599
  }

600
  if (FLAC__stream_encoder_set_metadata (flacenc->encoder,
601
          flacenc->meta, entries) != true)
602
    g_warning ("Dude, i'm already initialized!");
603

604
605
606
  gst_tag_list_free (copy);
}

607
static GstCaps *
Wim Taymans's avatar
Wim Taymans committed
608
gst_flac_enc_getcaps (GstAudioEncoder * enc, GstCaps * filter)
609
{
610
611
612
613
  GstCaps *ret = NULL, *caps = NULL;
  GstPad *pad;

  pad = GST_AUDIO_ENCODER_SINK_PAD (enc);
614
615
616

  GST_OBJECT_LOCK (pad);

Wim Taymans's avatar
Wim Taymans committed
617
618
  if (gst_pad_has_current_caps (pad)) {
    ret = gst_pad_get_current_caps (pad);
619
  } else {
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
    gint i;
    GValue v_arr = { 0, };
    GValue v = { 0, };
    GstStructure *s, *s2;

    g_value_init (&v_arr, GST_TYPE_ARRAY);
    g_value_init (&v, G_TYPE_STRING);

    g_value_set_string (&v, GST_AUDIO_NE (S8));
    gst_value_array_append_value (&v_arr, &v);
    g_value_set_string (&v, GST_AUDIO_NE (S16));
    gst_value_array_append_value (&v_arr, &v);
    g_value_set_string (&v, GST_AUDIO_NE (S24));
    gst_value_array_append_value (&v_arr, &v);
    g_value_set_string (&v, GST_AUDIO_NE (S32));
    gst_value_array_append_value (&v_arr, &v);
    g_value_unset (&v);

    s = gst_structure_new_empty ("audio/x-raw");
    gst_structure_set_value (s, "format", &v_arr);
    g_value_unset (&v_arr);

    gst_structure_set (s, "layout", G_TYPE_STRING, "interleaved",
        "rate", GST_TYPE_INT_RANGE, 1, 655350, NULL);
644
645

    ret = gst_caps_new_empty ();
646
647
    for (i = 1; i <= 8; i++) {
      s2 = gst_structure_copy (s);
648

649
650
651
652
      if (i == 1) {
        gst_structure_set (s, "channels", G_TYPE_INT, 1, NULL);
      } else {
        guint64 channel_mask;
653

654
655
656
657
        gst_audio_channel_positions_to_mask (channel_positions[i - 1], i,
            &channel_mask);
        gst_structure_set (s, "channels", G_TYPE_INT, 1, "channel-mask",
            GST_TYPE_BITMASK, channel_mask, NULL);
658
659
      }

660
      gst_caps_append_structure (ret, s2);
661
    }
662
    gst_structure_free (s);
663
664
665
666
667
668
  }

  GST_OBJECT_UNLOCK (pad);

  GST_DEBUG_OBJECT (pad, "Return caps %" GST_PTR_FORMAT, ret);

669
670
671
672
  caps = gst_audio_encoder_proxy_getcaps (enc, ret);
  gst_caps_unref (ret);

  return caps;
673
674
}

675
static guint64
676
gst_flac_enc_peer_query_total_samples (GstFlacEnc * flacenc, GstPad * pad)
677
678
{
  gint64 duration;
679
680
  GstAudioInfo *info =
      gst_audio_encoder_get_audio_info (GST_AUDIO_ENCODER (flacenc));
681

682
  GST_DEBUG_OBJECT (flacenc, "querying peer for DEFAULT format duration");
683
  if (gst_pad_peer_query_duration (pad, GST_FORMAT_DEFAULT, &duration)
Wim Taymans's avatar
Wim Taymans committed
684
      && duration != GST_CLOCK_TIME_NONE)
685
686
    goto done;

687
  GST_DEBUG_OBJECT (flacenc, "querying peer for TIME format duration");
688

689
  if (gst_pad_peer_query_duration (pad, GST_FORMAT_TIME, &duration)
Wim Taymans's avatar
Wim Taymans committed
690
      && duration != GST_CLOCK_TIME_NONE) {
691
692
    GST_DEBUG_OBJECT (flacenc, "peer reported duration %" GST_TIME_FORMAT,
        GST_TIME_ARGS (duration));
693
    duration = GST_CLOCK_TIME_TO_FRAMES (duration, GST_AUDIO_INFO_RATE (info));
694
695
696
697
698
699
700
701
702
703
704
705
706
707

    goto done;
  }

  GST_DEBUG_OBJECT (flacenc, "Upstream reported no total samples");
  return GST_CLOCK_TIME_NONE;

done:
  GST_DEBUG_OBJECT (flacenc,
      "Upstream reported %" G_GUINT64_FORMAT " total samples", duration);

  return duration;
}

708
static gboolean
709
gst_flac_enc_set_format (GstAudioEncoder * enc, GstAudioInfo * info)
710
711
{
  GstFlacEnc *flacenc;
712
  guint64 total_samples = GST_CLOCK_TIME_NONE;
713
  FLAC__StreamEncoderInitStatus init_status;
714
  GstCaps *caps;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
715

716
  flacenc = GST_FLAC_ENC (enc);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
717

718
  /* if configured again, means something changed, can't handle that */
719
720
  if (FLAC__stream_encoder_get_state (flacenc->encoder) !=
      FLAC__STREAM_ENCODER_UNINITIALIZED)
721
722
    goto encoder_already_initialized;

David Schleef's avatar
David Schleef committed
723
  caps = gst_caps_new_simple ("audio/x-flac",
724
725
      "channels", G_TYPE_INT, GST_AUDIO_INFO_CHANNELS (info),
      "rate", G_TYPE_INT, GST_AUDIO_INFO_RATE (info), NULL);
726

727
  if (!gst_pad_set_caps (GST_AUDIO_ENCODER_SRC_PAD (enc), caps))
728
729
730
    goto setting_src_caps_failed;

  gst_caps_unref (caps);
731

732
733
734
735
  gst_audio_get_channel_reorder_map (GST_AUDIO_INFO_CHANNELS (info),
      channel_positions[GST_AUDIO_INFO_CHANNELS (info) - 1], info->position,
      flacenc->channel_reorder_map);

736
  total_samples = gst_flac_enc_peer_query_total_samples (flacenc,
737
      GST_AUDIO_ENCODER_SINK_PAD (enc));
738

739
740
741
742
743
744
  FLAC__stream_encoder_set_bits_per_sample (flacenc->encoder,
      GST_AUDIO_INFO_WIDTH (info));
  FLAC__stream_encoder_set_sample_rate (flacenc->encoder,
      GST_AUDIO_INFO_RATE (info));
  FLAC__stream_encoder_set_channels (flacenc->encoder,
      GST_AUDIO_INFO_CHANNELS (info));
745
746
747

  if (total_samples != GST_CLOCK_TIME_NONE)
    FLAC__stream_encoder_set_total_samples_estimate (flacenc->encoder,
748
        MIN (total_samples, G_GUINT64_CONSTANT (0x0FFFFFFFFF)));
749

750
  gst_flac_enc_set_metadata (flacenc, total_samples);
751

752
753
754
755
  /* callbacks clear to go now;
   * write callbacks receives headers during init */
  flacenc->stopped = FALSE;

756
757
758
759
760
  init_status = FLAC__stream_encoder_init_stream (flacenc->encoder,
      gst_flac_enc_write_callback, gst_flac_enc_seek_callback,
      gst_flac_enc_tell_callback, NULL, flacenc);
  if (init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK)
    goto failed_to_initialize;
761

762
  /* no special feedback to base class; should provide all available samples */
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786

  return TRUE;

encoder_already_initialized:
  {
    g_warning ("flac already initialized -- fixme allow this");
    gst_object_unref (flacenc);
    return FALSE;
  }
setting_src_caps_failed:
  {
    GST_DEBUG_OBJECT (flacenc,
        "Couldn't set caps on source pad: %" GST_PTR_FORMAT, caps);
    gst_caps_unref (caps);
    gst_object_unref (flacenc);
    return FALSE;
  }
failed_to_initialize:
  {
    GST_ELEMENT_ERROR (flacenc, LIBRARY, INIT, (NULL),
        ("could not initialize encoder (wrong parameters?)"));
    gst_object_unref (flacenc);
    return FALSE;
  }
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
787
788
}

Wim Taymans's avatar
Wim Taymans committed
789
static gboolean
790
gst_flac_enc_update_quality (GstFlacEnc * flacenc, gint quality)
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
791
{
792
793
794
  GstAudioInfo *info =
      gst_audio_encoder_get_audio_info (GST_AUDIO_ENCODER (flacenc));

Wim Taymans's avatar
Wim Taymans committed
795
796
  flacenc->quality = quality;

797
798
799
800
801
802
803
804
805
806
#define DO_UPDATE(name, val, str)                                               \
  G_STMT_START {                                                                \
    if (FLAC__stream_encoder_get_##name (flacenc->encoder) !=                   \
        flacenc_params[quality].val) {                                          \
      FLAC__stream_encoder_set_##name (flacenc->encoder,                        \
          flacenc_params[quality].val);                                         \
      g_object_notify (G_OBJECT (flacenc), str);                                \
    }                                                                           \
  } G_STMT_END

Wim Taymans's avatar
Wim Taymans committed
807
808
  g_object_freeze_notify (G_OBJECT (flacenc));

809
810
  if (GST_AUDIO_INFO_CHANNELS (info) == 2
      || GST_AUDIO_INFO_CHANNELS (info) == 0) {
811
812
813
814
    DO_UPDATE (do_mid_side_stereo, mid_side, "mid_side_stereo");
    DO_UPDATE (loose_mid_side_stereo, loose_mid_side, "loose_mid_side");
  }

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
815
816
817
818
819
820
821
822
823
824
825
826
827
828
  DO_UPDATE (blocksize, blocksize, "blocksize");
  DO_UPDATE (max_lpc_order, max_lpc_order, "max_lpc_order");
  DO_UPDATE (qlp_coeff_precision, qlp_coeff_precision, "qlp_coeff_precision");
  DO_UPDATE (do_qlp_coeff_prec_search, qlp_coeff_prec_search,
      "qlp_coeff_prec_search");
  DO_UPDATE (do_escape_coding, escape_coding, "escape_coding");
  DO_UPDATE (do_exhaustive_model_search, exhaustive_model_search,
      "exhaustive_model_search");
  DO_UPDATE (min_residual_partition_order, min_residual_partition_order,
      "min_residual_partition_order");
  DO_UPDATE (max_residual_partition_order, max_residual_partition_order,
      "max_residual_partition_order");
  DO_UPDATE (rice_parameter_search_dist, rice_parameter_search_dist,
      "rice_parameter_search_dist");
Wim Taymans's avatar
Wim Taymans committed
829
830
831
832
833
834

#undef DO_UPDATE

  g_object_thaw_notify (G_OBJECT (flacenc));

  return TRUE;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
835
836
}

837
838
839
static FLAC__StreamEncoderSeekStatus
gst_flac_enc_seek_callback (const FLAC__StreamEncoder * encoder,
    FLAC__uint64 absolute_byte_offset, void *client_data)
840
{
841
  GstFlacEnc *flacenc;
842
843
  GstEvent *event;
  GstPad *peerpad;
Wim Taymans's avatar
Wim Taymans committed
844
  GstSegment seg;
845

846
  flacenc = GST_FLAC_ENC (client_data);
847

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
848
  if (flacenc->stopped)
849
    return FLAC__STREAM_ENCODER_SEEK_STATUS_OK;
850

Wim Taymans's avatar
Wim Taymans committed
851
852
853
854
855
  gst_segment_init (&seg, GST_FORMAT_BYTES);
  seg.start = absolute_byte_offset;
  seg.stop = GST_BUFFER_OFFSET_NONE;
  seg.time = 0;
  event = gst_event_new_segment (&seg);
856

857
  if ((peerpad = gst_pad_get_peer (GST_AUDIO_ENCODER_SRC_PAD (flacenc)))) {
858
    gboolean ret = gst_pad_send_event (peerpad, event);
859

860
    gst_object_unref (peerpad);
861

862
    if (ret) {
Josep Torra's avatar
Josep Torra committed
863
864
      GST_DEBUG ("Seek to %" G_GUINT64_FORMAT " %s",
          (guint64) absolute_byte_offset, "succeeded");
865
    } else {
Josep Torra's avatar
Josep Torra committed
866
867
      GST_DEBUG ("Seek to %" G_GUINT64_FORMAT " %s",
          (guint64) absolute_byte_offset, "failed");
868
      return FLAC__STREAM_ENCODER_SEEK_STATUS_UNSUPPORTED;
869
    }
870
871
  } else {
    GST_DEBUG ("Seek to %" G_GUINT64_FORMAT " failed (no peer pad)",
Josep Torra's avatar
Josep Torra committed
872
        (guint64) absolute_byte_offset);
873
  }
874
875

  flacenc->offset = absolute_byte_offset;
876
  return FLAC__STREAM_ENCODER_SEEK_STATUS_OK;
877
878
}

879
880
881
882
883
884
885
886
887
static void
notgst_value_array_append_buffer (GValue * array_val, GstBuffer * buf)
{
  GValue value = { 0, };

  g_value_init (&value, GST_TYPE_BUFFER);
  /* copy buffer to avoid problems with circular refcounts */
  buf = gst_buffer_copy (buf);
  /* again, for good measure */
Wim Taymans's avatar
Wim Taymans committed
888
  GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_HEADER);
889
890
891
892
893
894
895
896
897
  gst_value_set_buffer (&value, buf);
  gst_buffer_unref (buf);
  gst_value_array_append_value (array_val, &value);
  g_value_unset (&value);
}

#define HDR_TYPE_STREAMINFO     0
#define HDR_TYPE_VORBISCOMMENT  4

898
static GstFlowReturn
899
900
901
902
903
904
905
906
gst_flac_enc_process_stream_headers (GstFlacEnc * enc)
{
  GstBuffer *vorbiscomment = NULL;
  GstBuffer *streaminfo = NULL;
  GstBuffer *marker = NULL;
  GValue array = { 0, };
  GstCaps *caps;
  GList *l;
907
  GstFlowReturn ret = GST_FLOW_OK;
908
909
  GstAudioInfo *info =
      gst_audio_encoder_get_audio_info (GST_AUDIO_ENCODER (enc));
910
911

  caps = gst_caps_new_simple ("audio/x-flac",
912
913
      "channels", G_TYPE_INT, GST_AUDIO_INFO_CHANNELS (info),
      "rate", G_TYPE_INT, GST_AUDIO_INFO_RATE (info), NULL);
914
915

  for (l = enc->headers; l != NULL; l = l->next) {
Wim Taymans's avatar
Wim Taymans committed
916
    GstBuffer *buf;
Wim Taymans's avatar
Wim Taymans committed
917
    GstMapInfo map;
Wim Taymans's avatar
Wim Taymans committed
918
919
    guint8 *data;
    gsize size;
920
921
922

    /* mark buffers so oggmux will ignore them if it already muxed the
     * header buffers from the streamheaders field in the caps */
Wim Taymans's avatar
Wim Taymans committed
923
    l->data = gst_buffer_make_writable (GST_BUFFER_CAST (l->data));
924

Wim Taymans's avatar
Wim Taymans committed
925
    buf = GST_BUFFER_CAST (l->data);
Wim Taymans's avatar
Wim Taymans committed
926
    GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_HEADER);
Wim Taymans's avatar
Wim Taymans committed
927

Wim Taymans's avatar
Wim Taymans committed
928
929
930
    gst_buffer_map (buf, &map, GST_MAP_READ);
    data = map.data;
    size = map.size;
931
932
933

    /* find initial 4-byte marker which we need to skip later on */
    if (size == 4 && memcmp (data, "fLaC", 4) == 0) {
Wim Taymans's avatar
Wim Taymans committed
934
      marker = buf;
935
    } else if (size > 1 && (data[0] & 0x7f) == HDR_TYPE_STREAMINFO) {
Wim Taymans's avatar
Wim Taymans committed
936
      streaminfo = buf;
937
    } else if (size > 1 && (data[0] & 0x7f) == HDR_TYPE_VORBISCOMMENT) {
Wim Taymans's avatar
Wim Taymans committed
938
      vorbiscomment = buf;
939
    }
Wim Taymans's avatar
Wim Taymans committed
940

Wim Taymans's avatar
Wim Taymans committed
941
    gst_buffer_unmap (buf, &map);
942
943
944
945
946
947
948
949
950
951
952
953
954
955
  }

  if (marker == NULL || streaminfo == NULL || vorbiscomment == NULL) {
    GST_WARNING_OBJECT (enc, "missing header %p %p %p, muxing into container "
        "formats may be broken", marker, streaminfo, vorbiscomment);
    goto push_headers;
  }

  g_value_init (&array, GST_TYPE_ARRAY);

  /* add marker including STREAMINFO header */
  {
    GstBuffer *buf;
    guint16 num;
Wim Taymans's avatar
Wim Taymans committed
956
    GstMapInfo map;
Wim Taymans's avatar
Wim Taymans committed
957
    guint8 *bdata;
Wim Taymans's avatar
Wim Taymans committed
958
    gsize slen;
959
960
961
962

    /* minus one for the marker that is merged with streaminfo here */
    num = g_list_length (enc->headers) - 1;

Wim Taymans's avatar
Wim Taymans committed
963
964
965
    slen = gst_buffer_get_size (streaminfo);
    buf = gst_buffer_new_and_alloc (13 + slen);

Wim Taymans's avatar
Wim Taymans committed
966
967
    gst_buffer_map (buf, &map, GST_MAP_WRITE);
    bdata = map.data;
Wim Taymans's avatar
Wim Taymans committed
968
969
970
971
972
973
974
975
    bdata[0] = 0x7f;
    memcpy (bdata + 1, "FLAC", 4);
    bdata[5] = 0x01;            /* mapping version major */
    bdata[6] = 0x00;            /* mapping version minor */
    bdata[7] = (num & 0xFF00) >> 8;
    bdata[8] = (num & 0x00FF) >> 0;
    memcpy (bdata + 9, "fLaC", 4);
    gst_buffer_extract (streaminfo, 0, bdata + 13, slen);
Wim Taymans's avatar
Wim Taymans committed
976
    gst_buffer_unmap (buf, &map);
Wim Taymans's avatar
Wim Taymans committed
977

978
979
980
981
982
983
984
985
986
    notgst_value_array_append_buffer (&array, buf);
    gst_buffer_unref (buf);
  }

  /* add VORBISCOMMENT header */
  notgst_value_array_append_buffer (&array, vorbiscomment);

  /* add other headers, if there are any */
  for (l = enc->headers; l != NULL; l = l->next) {
Wim Taymans's avatar
Wim Taymans committed
987
988
989
990
    GstBuffer *buf = GST_BUFFER_CAST (l->data);

    if (buf != marker && buf != streaminfo && buf != vorbiscomment) {
      notgst_value_array_append_buffer (&array, buf);
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
    }
  }

  gst_structure_set_value (gst_caps_get_structure (caps, 0),
      "streamheader", &array);
  g_value_unset (&array);

push_headers:

  /* push header buffers; update caps, so when we push the first buffer the
   * negotiated caps will change to caps that include the streamheader field */
  for (l = enc->headers; l != NULL; l = l->next) {
    GstBuffer *buf;

    buf = GST_BUFFER (l->data);
1006
1007
    GST_LOG_OBJECT (enc,
        "Pushing header buffer, size %" G_GSIZE_FORMAT " bytes",
Wim Taymans's avatar
Wim Taymans committed
1008
1009
        gst_buffer_get_size (buf));
#if 0
1010
1011
    GST_MEMDUMP_OBJECT (enc, "header buffer", GST_BUFFER_DATA (buf),
        GST_BUFFER_SIZE (buf));
Wim Taymans's avatar
Wim Taymans committed
1012
#endif
1013
    ret = gst_pad_push (GST_AUDIO_ENCODER_SRC_PAD (enc), buf);
1014
1015
1016
1017
1018
1019
    l->data = NULL;
  }
  g_list_free (enc->headers);
  enc->headers = NULL;

  gst_caps_unref (caps);
1020
1021

  return ret;
1022
1023
}

1024
1025
1026
1027
static FLAC__StreamEncoderWriteStatus
gst_flac_enc_write_callback (const FLAC__StreamEncoder * encoder,
    const FLAC__byte buffer[], size_t bytes,
    unsigned samples, unsigned current_frame, void *client_data)
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
1028
{
1029
  GstFlowReturn ret = GST_FLOW_OK;
1030
  GstFlacEnc *flacenc;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
1031
  GstBuffer *outbuf;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
1032

1033
  flacenc = GST_FLAC_ENC (client_data);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
1034

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
1035
  if (flacenc->stopped)
1036
    return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
Wim Taymans's avatar
Wim Taymans committed
1037

1038
  outbuf = gst_buffer_new_and_alloc (bytes);
Wim Taymans's avatar
Wim Taymans committed
1039
  gst_buffer_fill (outbuf, 0, buffer, bytes);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
1040

1041
1042
1043
  /* we assume libflac passes us stuff neatly framed */
  if (!flacenc->got_headers) {
    if