gstflacenc.c 46.4 KB
Newer Older
Andy Wingo Wingo's avatar
Andy Wingo 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>
48
#include <gst/audio/multichannel.h>
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
49
#include <gst/tag/tag.h>
50
#include <gst/gsttagsetter.h>
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
/* Taken from http://flac.sourceforge.net/format.html#frame_header */
static const GstAudioChannelPosition channel_positions[8][8] = {
  {GST_AUDIO_CHANNEL_POSITION_FRONT_MONO},
  {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,
        GST_AUDIO_CHANNEL_POSITION_LFE,
        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,
        GST_AUDIO_CHANNEL_POSITION_LFE,
      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,
        GST_AUDIO_CHANNEL_POSITION_LFE,
        GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT,
      GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT}
};
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
93

94
#define FLAC_SINK_CAPS \
95
96
97
98
99
100
101
  "audio/x-raw-int, "               \
  "endianness = (int) BYTE_ORDER, " \
  "signed = (boolean) TRUE, "       \
  "width = (int) 8, "               \
  "depth = (int) 8, "               \
  "rate = (int) [ 1, 655350 ], "    \
  "channels = (int) [ 1, 8 ]; "     \
102
103
104
105
  "audio/x-raw-int, "               \
  "endianness = (int) BYTE_ORDER, " \
  "signed = (boolean) TRUE, "       \
  "width = (int) 16, "              \
106
107
108
109
110
111
112
113
114
  "depth = (int) { 12, 16 }, "      \
  "rate = (int) [ 1, 655350 ], "    \
  "channels = (int) [ 1, 8 ]; "     \
  "audio/x-raw-int, "               \
  "endianness = (int) BYTE_ORDER, " \
  "signed = (boolean) TRUE, "       \
  "width = (int) 32, "              \
  "depth = (int) { 20, 24 }, "      \
  "rate = (int) [ 1, 655350 ], "    \
115
  "channels = (int) [ 1, 8 ]"
116
117
118
119
120
121
122
123
124
125

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,
126
    GST_STATIC_CAPS (FLAC_SINK_CAPS)
127
128
    );

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
129
130
enum
{
131
132
133
134
135
136
137
138
139
140
141
142
143
  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,
144
  PROP_RICE_PARAMETER_SEARCH_DIST,
145
146
  PROP_PADDING,
  PROP_SEEKPOINTS
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
147
148
};

149
150
151
GST_DEBUG_CATEGORY_STATIC (flacenc_debug);
#define GST_CAT_DEFAULT flacenc_debug

152
153
154
155
156
157
158
159
160
161
162
163

#define _do_init(type)                                                          \
  G_STMT_START{                                                                 \
    static const GInterfaceInfo tag_setter_info = {                             \
      NULL,                                                                     \
      NULL,                                                                     \
      NULL                                                                      \
    };                                                                          \
    g_type_add_interface_static (type, GST_TYPE_TAG_SETTER,                     \
                                 &tag_setter_info);                             \
  }G_STMT_END

164
165
GST_BOILERPLATE_FULL (GstFlacEnc, gst_flac_enc, GstAudioEncoder,
    GST_TYPE_AUDIO_ENCODER, _do_init);
166

167
168
169
170
171
172
173
174
175
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);
static GstCaps *gst_flac_enc_getcaps (GstAudioEncoder * enc);
static gboolean gst_flac_enc_sink_event (GstAudioEncoder * enc,
    GstEvent * event);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
176

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

179
180
181
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
182
    const GValue * value, GParamSpec * pspec);
183
static void gst_flac_enc_get_property (GObject * object, guint prop_id,
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
184
185
    GValue * value, GParamSpec * pspec);

186
187
188
189
190
191
192
193
194
195
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
196

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
197
198
199
200
201
202
203
204
205
206
207
208
209
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;
210
}
211
GstFlacEncParams;
Wim Taymans's avatar
Wim Taymans committed
212

213
static const GstFlacEncParams flacenc_params[] = {
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
214
215
216
217
218
219
220
221
222
223
  {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
224
225
226
};

#define DEFAULT_QUALITY 5
227
#define DEFAULT_PADDING 0
228
#define DEFAULT_SEEKPOINTS 0
Wim Taymans's avatar
Wim Taymans committed
229

230
#define GST_TYPE_FLAC_ENC_QUALITY (gst_flac_enc_quality_get_type ())
231
static GType
232
gst_flac_enc_quality_get_type (void)
Wim Taymans's avatar
Wim Taymans committed
233
234
{
  static GType qtype = 0;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
235

Wim Taymans's avatar
Wim Taymans committed
236
237
  if (qtype == 0) {
    static const GEnumValue values[] = {
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
238
      {0, "0 - Fastest compression", "0"},
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
239
240
241
242
      {1, "1", "1"},
      {2, "2", "2"},
      {3, "3", "3"},
      {4, "4", "4"},
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
243
      {5, "5 - Default", "5"},
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
244
245
      {6, "6", "6"},
      {7, "7", "7"},
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
246
247
      {8, "8 - Highest compression", "8"},
      {9, "9 - Insane", "9"},
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
248
      {0, NULL, NULL}
Wim Taymans's avatar
Wim Taymans committed
249
    };
250

251
    qtype = g_enum_register_static ("GstFlacEncQuality", values);
Wim Taymans's avatar
Wim Taymans committed
252
253
254
255
  }
  return qtype;
}

256
static void
257
gst_flac_enc_base_init (gpointer g_class)
258
259
260
{
  GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);

261
262
  gst_element_class_add_static_pad_template (element_class, &src_factory);
  gst_element_class_add_static_pad_template (element_class, &sink_factory);
263

264
265
266
267
  gst_element_class_set_details_simple (element_class, "FLAC audio encoder",
      "Codec/Encoder/Audio",
      "Encodes audio with the FLAC lossless audio encoder",
      "Wim Taymans <wim.taymans@chello.be>");
268
269
270

  GST_DEBUG_CATEGORY_INIT (flacenc_debug, "flacenc", 0,
      "Flac encoding element");
271
272
}

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
273
static void
274
gst_flac_enc_class_init (GstFlacEncClass * klass)
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
275
276
{
  GObjectClass *gobject_class;
277
  GstAudioEncoderClass *base_class;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
278

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
279
  gobject_class = (GObjectClass *) klass;
280
  base_class = (GstAudioEncoderClass *) (klass);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
281

282
283
284
  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
285

286
287
288
289
290
291
292
  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);

293
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_QUALITY,
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
294
      g_param_spec_enum ("quality",
295
296
          "Quality",
          "Speed versus compression tradeoff",
297
          GST_TYPE_FLAC_ENC_QUALITY, DEFAULT_QUALITY,
298
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
299
  g_object_class_install_property (G_OBJECT_CLASS (klass),
300
      PROP_STREAMABLE_SUBSET, g_param_spec_boolean ("streamable-subset",
301
302
          "Streamable subset",
          "true to limit encoder to generating a Subset stream, else false",
303
304
          TRUE,
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
305
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_MID_SIDE_STEREO,
306
      g_param_spec_boolean ("mid-side-stereo", "Do mid side stereo",
307
          "Do mid side stereo (only for stereo input)",
308
          flacenc_params[DEFAULT_QUALITY].mid_side,
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_LOOSE_MID_SIDE_STEREO, g_param_spec_boolean ("loose-mid-side-stereo",
312
          "Loose mid side stereo", "Loose mid side stereo",
313
          flacenc_params[DEFAULT_QUALITY].loose_mid_side,
314
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
315
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_BLOCKSIZE,
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
316
      g_param_spec_uint ("blocksize", "Blocksize", "Blocksize in samples",
317
          FLAC__MIN_BLOCK_SIZE, FLAC__MAX_BLOCK_SIZE,
318
          flacenc_params[DEFAULT_QUALITY].blocksize,
319
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
320
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_MAX_LPC_ORDER,
321
      g_param_spec_uint ("max-lpc-order", "Max LPC order",
322
323
          "Max LPC order; 0 => use only fixed predictors", 0,
          FLAC__MAX_LPC_ORDER, flacenc_params[DEFAULT_QUALITY].max_lpc_order,
324
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
325
  g_object_class_install_property (G_OBJECT_CLASS (klass),
326
      PROP_QLP_COEFF_PRECISION, g_param_spec_uint ("qlp-coeff-precision",
327
328
329
          "QLP coefficients precision",
          "Precision in bits of quantized linear-predictor coefficients; 0 = automatic",
          0, 32, flacenc_params[DEFAULT_QUALITY].qlp_coeff_precision,
330
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
331
  g_object_class_install_property (G_OBJECT_CLASS (klass),
332
      PROP_QLP_COEFF_PREC_SEARCH, g_param_spec_boolean ("qlp-coeff-prec-search",
333
334
335
336
          "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,
337
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
338
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_ESCAPE_CODING,
339
      g_param_spec_boolean ("escape-coding", "Do Escape coding",
340
341
          "search for escape codes in the entropy coding stage "
          "for slightly better compression",
342
          flacenc_params[DEFAULT_QUALITY].escape_coding,
343
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
344
  g_object_class_install_property (G_OBJECT_CLASS (klass),
345
      PROP_EXHAUSTIVE_MODEL_SEARCH,
346
      g_param_spec_boolean ("exhaustive-model-search",
347
348
349
          "Do exhaustive model search",
          "do exhaustive search of LP coefficient quantization (expensive!)",
          flacenc_params[DEFAULT_QUALITY].exhaustive_model_search,
350
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
351
  g_object_class_install_property (G_OBJECT_CLASS (klass),
352
      PROP_MIN_RESIDUAL_PARTITION_ORDER,
353
      g_param_spec_uint ("min-residual-partition-order",
354
355
356
          "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,
357
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
358
  g_object_class_install_property (G_OBJECT_CLASS (klass),
359
      PROP_MAX_RESIDUAL_PARTITION_ORDER,
360
      g_param_spec_uint ("max-residual-partition-order",
361
362
363
          "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,
364
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
365
  g_object_class_install_property (G_OBJECT_CLASS (klass),
366
      PROP_RICE_PARAMETER_SEARCH_DIST,
367
      g_param_spec_uint ("rice-parameter-search-dist",
368
369
370
371
          "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,
372
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
Wim Taymans's avatar
Wim Taymans committed
373

374
375
376
377
378
379
380
381
382
383
384
385
  /**
   * 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,
386
387
          DEFAULT_PADDING,
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
388

389
  /**
390
   * GstFlacEnc:seekpoints
391
   *
392
393
   * Write a SEEKTABLE block with a specific number of seekpoints
   * or with a specific interval spacing.
394
395
396
397
398
399
400
401
402
   *
   * 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,
403
404
          DEFAULT_SEEKPOINTS,
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
405
406
}

Wim Taymans's avatar
Wim Taymans committed
407
static void
408
gst_flac_enc_init (GstFlacEnc * flacenc, GstFlacEncClass * klass)
Wim Taymans's avatar
Wim Taymans committed
409
{
410
  GstAudioEncoder *enc = GST_AUDIO_ENCODER (flacenc);
Wim Taymans's avatar
Wim Taymans committed
411

412
  flacenc->encoder = FLAC__stream_encoder_new ();
413
  gst_flac_enc_update_quality (flacenc, DEFAULT_QUALITY);
414
415
416
417

  /* 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
418
419
420
}

static void
421
gst_flac_enc_finalize (GObject * object)
Wim Taymans's avatar
Wim Taymans committed
422
{
423
  GstFlacEnc *flacenc = GST_FLAC_ENC (object);
Wim Taymans's avatar
Wim Taymans committed
424

425
  FLAC__stream_encoder_delete (flacenc->encoder);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
426

427
  G_OBJECT_CLASS (parent_class)->finalize (object);
Wim Taymans's avatar
Wim Taymans committed
428
429
}

430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
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->channels = 0;
  flacenc->depth = 0;
  flacenc->sample_rate = 0;
  flacenc->eos = FALSE;
  flacenc->tags = gst_tag_list_new ();

  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;

478
479
  gst_tag_setter_reset_tags (GST_TAG_SETTER (enc));

480
481
482
  return TRUE;
}

483
484
485
486
487
static void
add_one_tag (const GstTagList * list, const gchar * tag, gpointer user_data)
{
  GList *comments;
  GList *it;
488
  GstFlacEnc *flacenc = GST_FLAC_ENC (user_data);
489

490
491
492
  /* IMAGE and PREVIEW_IMAGE tags are already written
   * differently, no need to store them inside the
   * vorbiscomments too */
493
494
  if (strcmp (tag, GST_TAG_IMAGE) == 0
      || strcmp (tag, GST_TAG_PREVIEW_IMAGE) == 0)
495
496
    return;

497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
  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
512
gst_flac_enc_set_metadata (GstFlacEnc * flacenc, guint64 total_samples)
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
513
{
514
515
  const GstTagList *user_tags;
  GstTagList *copy;
516
  gint entries = 1;
517
  gint n_images, n_preview_images;
518
519

  g_return_if_fail (flacenc != NULL);
520
  user_tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (flacenc));
521
522
523
524
  if ((flacenc->tags == NULL) && (user_tags == NULL)) {
    return;
  }
  copy = gst_tag_list_merge (user_tags, flacenc->tags,
525
      gst_tag_setter_get_tag_merge_mode (GST_TAG_SETTER (flacenc)));
526
527
528
529
530
  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);
531
532
533
534
535

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

536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
  if (n_images + n_preview_images > 0) {
    GstBuffer *buffer;
    GstCaps *caps;
    GstStructure *structure;
    GstTagImageType image_type = GST_TAG_IMAGE_TYPE_NONE;
    gint i;

    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);

      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;

      FLAC__metadata_object_picture_set_data (flacenc->meta[entries],
          GST_BUFFER_DATA (buffer), GST_BUFFER_SIZE (buffer), TRUE);
      /* 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);
      gst_buffer_unref (buffer);
      entries++;
    }
  }

580
581
582
583
  if (flacenc->seekpoints && total_samples != GST_CLOCK_TIME_NONE) {
    gboolean res;
    guint samples;

584
    flacenc->meta[entries] =
585
586
587
588
        FLAC__metadata_object_new (FLAC__METADATA_TYPE_SEEKTABLE);
    if (flacenc->seekpoints > 0) {
      res =
          FLAC__metadata_object_seektable_template_append_spaced_points
589
          (flacenc->meta[entries], flacenc->seekpoints, total_samples);
590
591
592
593
    } else {
      samples = -flacenc->seekpoints * flacenc->sample_rate;
      res =
          FLAC__metadata_object_seektable_template_append_spaced_points_by_samples
594
          (flacenc->meta[entries], samples, total_samples);
595
596
597
598
599
    }
    if (!res) {
      GST_DEBUG_OBJECT (flacenc, "adding seekpoint template %d failed",
          flacenc->seekpoints);
      FLAC__metadata_object_delete (flacenc->meta[1]);
600
      flacenc->meta[entries] = NULL;
601
602
603
604
605
606
607
    } else {
      entries++;
    }
  } else if (flacenc->seekpoints && total_samples == GST_CLOCK_TIME_NONE) {
    GST_WARNING_OBJECT (flacenc, "total time unknown; can not add seekpoints");
  }

608
  if (flacenc->padding > 0) {
609
610
611
612
    flacenc->meta[entries] =
        FLAC__metadata_object_new (FLAC__METADATA_TYPE_PADDING);
    flacenc->meta[entries]->length = flacenc->padding;
    entries++;
613
614
  }

615
  if (FLAC__stream_encoder_set_metadata (flacenc->encoder,
616
          flacenc->meta, entries) != true)
617
    g_warning ("Dude, i'm already initialized!");
618

619
620
621
  gst_tag_list_free (copy);
}

622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
static void
gst_flac_enc_caps_append_structure_with_widths (GstCaps * caps,
    GstStructure * s)
{
  GstStructure *tmp;
  GValue list = { 0, };
  GValue depth = { 0, };


  tmp = gst_structure_copy (s);
  gst_structure_set (tmp, "width", G_TYPE_INT, 8, "depth", G_TYPE_INT, 8, NULL);
  gst_caps_append_structure (caps, tmp);

  tmp = gst_structure_copy (s);

  g_value_init (&depth, G_TYPE_INT);
  g_value_init (&list, GST_TYPE_LIST);
  g_value_set_int (&depth, 12);
  gst_value_list_append_value (&list, &depth);
  g_value_set_int (&depth, 16);
  gst_value_list_append_value (&list, &depth);

  gst_structure_set (tmp, "width", G_TYPE_INT, 16, NULL);
  gst_structure_set_value (tmp, "depth", &list);
  gst_caps_append_structure (caps, tmp);

  g_value_reset (&list);

  tmp = s;

  g_value_set_int (&depth, 20);
  gst_value_list_append_value (&list, &depth);
  g_value_set_int (&depth, 24);
  gst_value_list_append_value (&list, &depth);

  gst_structure_set (tmp, "width", G_TYPE_INT, 32, NULL);
  gst_structure_set_value (tmp, "depth", &list);
  gst_caps_append_structure (caps, tmp);

  g_value_unset (&list);
  g_value_unset (&depth);
}

665
static GstCaps *
666
gst_flac_enc_getcaps (GstAudioEncoder * enc)
667
{
668
669
670
671
  GstCaps *ret = NULL, *caps = NULL;
  GstPad *pad;

  pad = GST_AUDIO_ENCODER_SINK_PAD (enc);
672
673
674
675
676
677
678
679
680
681

  GST_OBJECT_LOCK (pad);

  if (GST_PAD_CAPS (pad)) {
    ret = gst_caps_ref (GST_PAD_CAPS (pad));
  } else {
    gint i, c;

    ret = gst_caps_new_empty ();

682
683
    gst_flac_enc_caps_append_structure_with_widths (ret,
        gst_structure_new ("audio/x-raw-int",
684
685
            "endianness", G_TYPE_INT, G_BYTE_ORDER,
            "signed", G_TYPE_BOOLEAN, TRUE,
686
            "rate", GST_TYPE_INT_RANGE, 1, 655350,
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
            "channels", GST_TYPE_INT_RANGE, 1, 2, NULL));

    for (i = 3; i <= 8; i++) {
      GValue positions = { 0, };
      GValue pos = { 0, };
      GstStructure *s;

      g_value_init (&positions, GST_TYPE_ARRAY);
      g_value_init (&pos, GST_TYPE_AUDIO_CHANNEL_POSITION);

      for (c = 0; c < i; c++) {
        g_value_set_enum (&pos, channel_positions[i - 1][c]);
        gst_value_array_append_value (&positions, &pos);
      }
      g_value_unset (&pos);

      s = gst_structure_new ("audio/x-raw-int",
          "endianness", G_TYPE_INT, G_BYTE_ORDER,
          "signed", G_TYPE_BOOLEAN, TRUE,
706
          "rate", GST_TYPE_INT_RANGE, 1, 655350,
707
708
709
710
          "channels", G_TYPE_INT, i, NULL);
      gst_structure_set_value (s, "channel-positions", &positions);
      g_value_unset (&positions);

711
      gst_flac_enc_caps_append_structure_with_widths (ret, s);
712
713
714
715
716
717
718
    }
  }

  GST_OBJECT_UNLOCK (pad);

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

719
720
721
722
  caps = gst_audio_encoder_proxy_getcaps (enc, ret);
  gst_caps_unref (ret);

  return caps;
723
724
}

725
726
727
728
729
730
static guint64
gst_flac_enc_query_peer_total_samples (GstFlacEnc * flacenc, GstPad * pad)
{
  GstFormat fmt = GST_FORMAT_DEFAULT;
  gint64 duration;

731
  GST_DEBUG_OBJECT (flacenc, "querying peer for DEFAULT format duration");
732
733
734
735
736
  if (gst_pad_query_peer_duration (pad, &fmt, &duration)
      && fmt == GST_FORMAT_DEFAULT && duration != GST_CLOCK_TIME_NONE)
    goto done;

  fmt = GST_FORMAT_TIME;
737
  GST_DEBUG_OBJECT (flacenc, "querying peer for TIME format duration");
738
739
740

  if (gst_pad_query_peer_duration (pad, &fmt, &duration) &&
      fmt == GST_FORMAT_TIME && duration != GST_CLOCK_TIME_NONE) {
741
742
743
    GST_DEBUG_OBJECT (flacenc, "peer reported duration %" GST_TIME_FORMAT,
        GST_TIME_ARGS (duration));
    duration = GST_CLOCK_TIME_TO_FRAMES (duration, flacenc->sample_rate);
744
745
746
747
748
749
750
751
752
753
754
755
756
757

    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;
}

758
static gboolean
759
gst_flac_enc_set_format (GstAudioEncoder * enc, GstAudioInfo * info)
760
761
{
  GstFlacEnc *flacenc;
762
  guint64 total_samples = GST_CLOCK_TIME_NONE;
763
  FLAC__StreamEncoderInitStatus init_status;
764
  GstCaps *caps;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
765

766
  flacenc = GST_FLAC_ENC (enc);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
767

768
  /* if configured again, means something changed, can't handle that */
769
770
  if (FLAC__stream_encoder_get_state (flacenc->encoder) !=
      FLAC__STREAM_ENCODER_UNINITIALIZED)
771
772
    goto encoder_already_initialized;

773
774
775
776
  flacenc->channels = GST_AUDIO_INFO_CHANNELS (info);
  flacenc->width = GST_AUDIO_INFO_WIDTH (info);
  flacenc->depth = GST_AUDIO_INFO_DEPTH (info);
  flacenc->sample_rate = GST_AUDIO_INFO_RATE (info);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
777

David Schleef's avatar
David Schleef committed
778
779
780
  caps = gst_caps_new_simple ("audio/x-flac",
      "channels", G_TYPE_INT, flacenc->channels,
      "rate", G_TYPE_INT, flacenc->sample_rate, NULL);
781

782
  if (!gst_pad_set_caps (GST_AUDIO_ENCODER_SRC_PAD (enc), caps))
783
784
785
    goto setting_src_caps_failed;

  gst_caps_unref (caps);
786

787
788
  total_samples = gst_flac_enc_query_peer_total_samples (flacenc,
      GST_AUDIO_ENCODER_SINK_PAD (enc));
789

790
791
792
  FLAC__stream_encoder_set_bits_per_sample (flacenc->encoder, flacenc->depth);
  FLAC__stream_encoder_set_sample_rate (flacenc->encoder, flacenc->sample_rate);
  FLAC__stream_encoder_set_channels (flacenc->encoder, flacenc->channels);
793
794
795

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

798
  gst_flac_enc_set_metadata (flacenc, total_samples);
799

800
801
802
803
  /* callbacks clear to go now;
   * write callbacks receives headers during init */
  flacenc->stopped = FALSE;

804
805
806
807
808
  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;
809

810
  /* no special feedback to base class; should provide all available samples */
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831

  return TRUE;

encoder_already_initialized:
  {
    g_warning ("flac already initialized -- fixme allow this");
    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);
    return FALSE;
  }
failed_to_initialize:
  {
    GST_ELEMENT_ERROR (flacenc, LIBRARY, INIT, (NULL),
        ("could not initialize encoder (wrong parameters?)"));
    return FALSE;
  }
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
832
833
}

Wim Taymans's avatar
Wim Taymans committed
834
static gboolean
835
gst_flac_enc_update_quality (GstFlacEnc * flacenc, gint quality)
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
836
{
Wim Taymans's avatar
Wim Taymans committed
837
838
  flacenc->quality = quality;

839
840
841
842
843
844
845
846
847
848
#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
849
850
  g_object_freeze_notify (G_OBJECT (flacenc));

851
  if (flacenc->channels == 2 || flacenc->channels == 0) {
852
853
854
855
    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
856
857
858
859
860
861
862
863
864
865
866
867
868
869
  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
870
871
872
873
874
875

#undef DO_UPDATE

  g_object_thaw_notify (G_OBJECT (flacenc));

  return TRUE;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
876
877
}

878
879
880
static FLAC__StreamEncoderSeekStatus
gst_flac_enc_seek_callback (const FLAC__StreamEncoder * encoder,
    FLAC__uint64 absolute_byte_offset, void *client_data)
881
{
882
  GstFlacEnc *flacenc;
883
  GstPad *peerpad;
884

885
  flacenc = GST_FLAC_ENC (client_data);
886

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
887
  if (flacenc->stopped)
888
    return FLAC__STREAM_ENCODER_SEEK_STATUS_OK;
889

890
  if ((peerpad = gst_pad_get_peer (GST_AUDIO_ENCODER_SRC_PAD (flacenc)))) {
891
892
    GstEvent *event = gst_event_new_new_segment (TRUE, 1.0, GST_FORMAT_BYTES,
        absolute_byte_offset, GST_BUFFER_OFFSET_NONE, 0);
893
    gboolean ret = gst_pad_send_event (peerpad, event);
894

895
    gst_object_unref (peerpad);
896

897
    if (ret) {
Josep Torra's avatar
Josep Torra committed
898
899
      GST_DEBUG ("Seek to %" G_GUINT64_FORMAT " %s",
          (guint64) absolute_byte_offset, "succeeded");
900
    } else {
Josep Torra's avatar
Josep Torra committed
901
902
      GST_DEBUG ("Seek to %" G_GUINT64_FORMAT " %s",
          (guint64) absolute_byte_offset, "failed");
903
      return FLAC__STREAM_ENCODER_SEEK_STATUS_UNSUPPORTED;
904
    }
905
906
  } else {
    GST_DEBUG ("Seek to %" G_GUINT64_FORMAT " failed (no peer pad)",
Josep Torra's avatar
Josep Torra committed
907
        (guint64) absolute_byte_offset);
908
  }
909
910

  flacenc->offset = absolute_byte_offset;
911
  return FLAC__STREAM_ENCODER_SEEK_STATUS_OK;
912
913
}

914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
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 */
  GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_IN_CAPS);
  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

933
static GstFlowReturn
934
935
936
937
938
939
940
941
gst_flac_enc_process_stream_headers (GstFlacEnc * enc)
{
  GstBuffer *vorbiscomment = NULL;
  GstBuffer *streaminfo = NULL;
  GstBuffer *marker = NULL;
  GValue array = { 0, };
  GstCaps *caps;
  GList *l;
942
  GstFlowReturn ret = GST_FLOW_OK;
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024

  caps = gst_caps_new_simple ("audio/x-flac",
      "channels", G_TYPE_INT, enc->channels,
      "rate", G_TYPE_INT, enc->sample_rate, NULL);

  for (l = enc->headers; l != NULL; l = l->next) {
    const guint8 *data;
    guint size;

    /* mark buffers so oggmux will ignore them if it already muxed the
     * header buffers from the streamheaders field in the caps */
    l->data = gst_buffer_make_metadata_writable (GST_BUFFER (l->data));
    GST_BUFFER_FLAG_SET (GST_BUFFER (l->data), GST_BUFFER_FLAG_IN_CAPS);

    data = GST_BUFFER_DATA (GST_BUFFER_CAST (l->data));
    size = GST_BUFFER_SIZE (GST_BUFFER_CAST (l->data));

    /* find initial 4-byte marker which we need to skip later on */
    if (size == 4 && memcmp (data, "fLaC", 4) == 0) {
      marker = GST_BUFFER_CAST (l->data);
    } else if (size > 1 && (data[0] & 0x7f) == HDR_TYPE_STREAMINFO) {
      streaminfo = GST_BUFFER_CAST (l->data);
    } else if (size > 1 && (data[0] & 0x7f) == HDR_TYPE_VORBISCOMMENT) {
      vorbiscomment = GST_BUFFER_CAST (l->data);
    }
  }

  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;

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

    buf = gst_buffer_new_and_alloc (13 + GST_BUFFER_SIZE (streaminfo));
    GST_BUFFER_DATA (buf)[0] = 0x7f;
    memcpy (GST_BUFFER_DATA (buf) + 1, "FLAC", 4);
    GST_BUFFER_DATA (buf)[5] = 0x01;    /* mapping version major */
    GST_BUFFER_DATA (buf)[6] = 0x00;    /* mapping version minor */
    GST_BUFFER_DATA (buf)[7] = (num & 0xFF00) >> 8;
    GST_BUFFER_DATA (buf)[8] = (num & 0x00FF) >> 0;
    memcpy (GST_BUFFER_DATA (buf) + 9, "fLaC", 4);
    memcpy (GST_BUFFER_DATA (buf) + 13, GST_BUFFER_DATA (streaminfo),
        GST_BUFFER_SIZE (streaminfo));
    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) {
    if (GST_BUFFER_CAST (l->data) != marker &&
        GST_BUFFER_CAST (l->data) != streaminfo &&
        GST_BUFFER_CAST (l->data) != vorbiscomment) {
      notgst_value_array_append_buffer (&array, GST_BUFFER_CAST (l->data));
    }
  }

  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);
    gst_buffer_set_caps (buf, caps);
1025
1026
1027
1028
    GST_LOG_OBJECT (enc, "Pushing header buffer, size %u bytes",
        GST_BUFFER_SIZE (buf));
    GST_MEMDUMP_OBJECT (enc, "header buffer", GST_BUFFER_DATA (buf),
        GST_BUFFER_SIZE (buf));
1029
    ret = gst_pad_push (GST_AUDIO_ENCODER_SRC_PAD (enc), buf);
1030
1031
1032
1033
1034
1035
    l->data = NULL;
  }
  g_list_free (enc->headers);
  enc->headers = NULL;

  gst_caps_unref (caps);
1036
1037

  return ret;
1038
1039
}

1040
1041
1042
1043
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
1044
{
1045
  GstFlowReturn ret = GST_FLOW_OK;
1046
  GstFlacEnc *flacenc;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
1047
  GstBuffer *outbuf;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
1048

1049
  flacenc = GST_FLAC_ENC (client_data);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
1050

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
1051
  if (flacenc->stopped)
1052
    return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
Wim Taymans's avatar
Wim Taymans committed
1053

1054
  outbuf = gst_buffer_new_and_alloc (bytes);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
1055
1056
  memcpy (GST_BUFFER_DATA (outbuf), buffer, bytes);

1057
1058
1059
  /* we assume libflac passes us stuff neatly framed */
  if (!flacenc->got_headers) {
    if (samples == 0) {
1060
1061
      GST_DEBUG_OBJECT (flacenc, "Got header, queueing (%u bytes)",
          (guint) bytes);
1062
1063
1064
1065
1066
      flacenc->headers = g_list_append (flacenc->headers, outbuf);
      /* note: it's important that we increase our byte offset */
      goto out;
    } else {
      GST_INFO_OBJECT (flacenc, "Non-header packet, we have all headers now");
1067
      ret = gst_flac_enc_process_stream_headers (flacenc);
1068
1069
      flacenc->got_headers = TRUE;
    }
1070
1071
1072
  }

  if (flacenc->got_headers && samples == 0) {
1073
    /* header fixup, push downstream directly */
1074
1075
1076
1077
    GST_DEBUG_OBJECT (flacenc, "Fixing up headers at pos=%" G_GUINT64_FORMAT
        ", size=%u", flacenc->offset, (guint) bytes);
    GST_MEMDUMP_OBJECT (flacenc, "Presumed header fragment",
        GST_BUFFER_DATA (outbuf), GST_BUFFER_SIZE (outbuf));
1078
1079
1080
    gst_buffer_set_caps (outbuf,
        GST_PAD_CAPS (GST_AUDIO_ENCODER_SRC_PAD (flacenc)));
    ret = gst_pad_push (GST_AUDIO_ENCODER_SRC_PAD (flacenc), outbuf);
1081
  } else {
1082
    /* regular frame data, pass to base class */
1083
1084
1085
    GST_LOG ("Pushing buffer: ts=%" GST_TIME_FORMAT ", samples=%u, size=%u, "
        "pos=%" G_GUINT64_FORMAT, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)),
        samples, (guint) bytes, flacenc->offset);
1086
1087
    ret = gst_audio_encoder_finish_frame (GST_AUDIO_ENCODER (flacenc),
        outbuf, samples);
1088
1089
  }

1090
1091
1092
1093
1094
  if (ret != GST_FLOW_OK)
    GST_DEBUG_OBJECT (flacenc, "flow: %s", gst_flow_get_name (ret));

  flacenc->last_flow = ret;

1095
out:
1096
1097
  flacenc->offset += bytes;

1098
  if (ret != GST_FLOW_OK)
1099
1100
1101
    return FLAC__STREAM_ENCODER_WRITE_STATUS_FATAL_ERROR;

  return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
1102
1103
}

1104
1105
1106
static FLAC__StreamEncoderTellStatus
gst_flac_enc_tell_callback (const FLAC__StreamEncoder * encoder,
    FLAC__uint64 * absolute_byte_offset, void *client_data)
1107
{
1108
  GstFlacEnc *flacenc = GST_FLAC_ENC (client_data);
1109
1110

  *absolute_byte_offset = flacenc->offset;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
1111

1112
  return FLAC__STREAM_ENCODER_TELL_STATUS_OK;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
1113
1114
}

1115
static gboolean
1116
gst_flac_enc_sink_event (GstAudioEncoder * enc, GstEvent * event)
1117
{
1118
1119
  GstFlacEnc *flacenc;
  GstTagList *taglist;
1120
  gboolean ret = FALSE;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
1121

1122
  flacenc = GST_FLAC_ENC (enc);
1123

1124
1125
  GST_DEBUG ("Received %s event on sinkpad", GST_EVENT_TYPE_NAME (event));

1126
  switch (GST_EVENT_TYPE (event)) {
1127
1128
1129
1130
1131
    case GST_EVENT_NEWSEGMENT:{
      GstFormat format;
      gint64 start, stream_time;

      if (flacenc->offset == 0) {
1132
        gst_event_parse_new_segment (event, NULL, NULL, &format, &start, NULL,
1133
1134
1135
            &stream_time);
      } else {
        start = -1;
1136
        stream_time = -1;
1137
      }
1138
1139

      if (start > 0) {
1140
1141
1142
1143
1144
        if (flacenc->offset > 0)
          GST_DEBUG ("Not handling mid-stream newsegment event");
        else
          GST_DEBUG ("Not handling newsegment event with non-zero start");
      } else {
1145
        GstEvent *e = gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES,
1146
1147
            0, -1, 0);

1148
        ret = gst_pad_push_event (GST_AUDIO_ENCODER_SRC_PAD (enc), e);
1149
      }
1150
1151

      if (stream_time > 0) {
1152
1153
        GST_DEBUG ("Not handling non-zero stream time");
      }
1154

1155
      /* don't push it downstream, we'll generate our own via seek to 0 */
1156
1157
      gst_event_unref (event);
      ret = TRUE;
1158
1159
      break;
    }
1160
    case GST_EVENT_EOS:
1161
      flacenc->eos = TRUE;
1162
1163
1164
1165
      break;
    case GST_EVENT_TAG:
      if (flacenc->tags) {
        gst_event_parse_tag (event, &taglist);
1166
1167
        gst_tag_list_insert (flacenc->tags, taglist,
            gst_tag_setter_get_tag_merge_mode (GST_TAG_SETTER (flacenc)));
1168
1169
1170
1171
1172
1173
      } else {
        g_assert_not_reached ();
      }
      break;
    default:
      break;
1174
  }
1175

1176
  return ret;
1177
}
1178