gstflacenc.c 52.3 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
 * 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
Tim-Philipp Müller's avatar
Tim-Philipp Müller committed
16
17
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
18
 */
19
20
21
22
23
24
/**
 * SECTION:element-flacenc
 * @see_also: #GstFlacDec
 *
 * flacenc encodes FLAC streams.
 * <ulink url="http://flac.sourceforge.net/">FLAC</ulink>
25
26
 * is a Free Lossless Audio Codec. FLAC audio can directly be written into
 * a file, or embedded into containers such as oggmux or matroskamux.
27
28
29
30
 *
 * <refsect2>
 * <title>Example launch line</title>
 * |[
31
 * gst-launch-1.0 audiotestsrc num-buffers=100 ! flacenc ! filesink location=beep.flac
32
33
34
35
36
37
38
 * ]| Encode a short sine wave into FLAC
 * |[
 * gst-launch-1.0 cdparanoiasrc mode=continuous ! queue ! audioconvert ! flacenc ! filesink location=cd.flac
 * ]| Rip a whole audio CD into a single FLAC file, with the track table saved as a CUE sheet inside the FLAC file
 * |[
 * gst-launch-1.0 cdparanoiasrc track=5 ! queue ! audioconvert ! flacenc ! filesink location=track5.flac
 * ]| Rip track 5 of an audio CD and encode it losslessly to a FLAC file
39
40
 * </refsect2>
 */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
41

42
43
44
/* 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.
45
 */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
46

47
48
49
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
50
51
52
53
#include <stdlib.h>
#include <string.h>

#include <gstflacenc.h>
54
#include <gst/audio/audio.h>
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
55
#include <gst/tag/tag.h>
56
#include <gst/gsttagsetter.h>
57

58
59
/* Taken from http://flac.sourceforge.net/format.html#frame_header */
static const GstAudioChannelPosition channel_positions[8][8] = {
60
  {GST_AUDIO_CHANNEL_POSITION_MONO},
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
  {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,
78
        GST_AUDIO_CHANNEL_POSITION_LFE1,
79
80
81
82
83
84
85
86
87
        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,
88
        GST_AUDIO_CHANNEL_POSITION_LFE1,
89
90
91
92
93
94
      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,
95
        GST_AUDIO_CHANNEL_POSITION_LFE1,
96
97
98
        GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT,
      GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT}
};
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
99

100
101
102
103
104
105
static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("audio/x-flac")
    );

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
106
107
enum
{
108
109
110
111
112
113
114
115
116
117
118
119
120
  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,
121
  PROP_RICE_PARAMETER_SEARCH_DIST,
122
123
  PROP_PADDING,
  PROP_SEEKPOINTS
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
124
125
};

126
127
128
GST_DEBUG_CATEGORY_STATIC (flacenc_debug);
#define GST_CAT_DEFAULT flacenc_debug

Wim Taymans's avatar
Wim Taymans committed
129
#define gst_flac_enc_parent_class parent_class
130
G_DEFINE_TYPE_WITH_CODE (GstFlacEnc, gst_flac_enc, GST_TYPE_AUDIO_ENCODER,
Anton Belka's avatar
Anton Belka committed
131
132
133
    G_IMPLEMENT_INTERFACE (GST_TYPE_TAG_SETTER, NULL)
    G_IMPLEMENT_INTERFACE (GST_TYPE_TOC_SETTER, NULL)
    );
134

135
136
137
138
139
140
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
141
static GstCaps *gst_flac_enc_getcaps (GstAudioEncoder * enc, GstCaps * filter);
142
143
static gboolean gst_flac_enc_sink_event (GstAudioEncoder * enc,
    GstEvent * event);
144
145
static gboolean gst_flac_enc_sink_query (GstAudioEncoder * enc,
    GstQuery * query);
146

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

149
150
static GstCaps *gst_flac_enc_generate_sink_caps (void);

151
152
153
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
154
    const GValue * value, GParamSpec * pspec);
155
static void gst_flac_enc_get_property (GObject * object, guint prop_id,
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
156
157
    GValue * value, GParamSpec * pspec);

158
159
160
161
162
163
164
165
166
167
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
168

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
169
170
171
172
173
174
175
176
177
178
179
180
181
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;
182
}
183
GstFlacEncParams;
Wim Taymans's avatar
Wim Taymans committed
184

185
static const GstFlacEncParams flacenc_params[] = {
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
186
187
188
189
190
191
192
193
194
195
  {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
196
197
198
};

#define DEFAULT_QUALITY 5
199
#define DEFAULT_PADDING 0
200
#define DEFAULT_SEEKPOINTS -10
Wim Taymans's avatar
Wim Taymans committed
201

202
#define GST_TYPE_FLAC_ENC_QUALITY (gst_flac_enc_quality_get_type ())
203
static GType
204
gst_flac_enc_quality_get_type (void)
Wim Taymans's avatar
Wim Taymans committed
205
206
{
  static GType qtype = 0;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
207

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

223
    qtype = g_enum_register_static ("GstFlacEncQuality", values);
Wim Taymans's avatar
Wim Taymans committed
224
225
226
227
  }
  return qtype;
}

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
228
static void
229
gst_flac_enc_class_init (GstFlacEncClass * klass)
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
230
231
232
{
  GObjectClass *gobject_class;
  GstElementClass *gstelement_class;
233
  GstAudioEncoderClass *base_class;
234
235
  GstCaps *sink_caps;
  GstPadTemplate *sink_templ;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
236

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
237
238
  gobject_class = (GObjectClass *) klass;
  gstelement_class = (GstElementClass *) klass;
239
  base_class = (GstAudioEncoderClass *) (klass);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
240

Wim Taymans's avatar
Wim Taymans committed
241
242
243
  GST_DEBUG_CATEGORY_INIT (flacenc_debug, "flacenc", 0,
      "Flac encoding element");

244
245
246
  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
247

248
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_QUALITY,
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
249
      g_param_spec_enum ("quality",
250
251
          "Quality",
          "Speed versus compression tradeoff",
252
          GST_TYPE_FLAC_ENC_QUALITY, DEFAULT_QUALITY,
253
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
254
  g_object_class_install_property (G_OBJECT_CLASS (klass),
255
      PROP_STREAMABLE_SUBSET, g_param_spec_boolean ("streamable-subset",
256
257
          "Streamable subset",
          "true to limit encoder to generating a Subset stream, else false",
258
259
          TRUE,
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
260
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_MID_SIDE_STEREO,
261
      g_param_spec_boolean ("mid-side-stereo", "Do mid side stereo",
262
          "Do mid side stereo (only for stereo input)",
263
          flacenc_params[DEFAULT_QUALITY].mid_side,
264
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
265
  g_object_class_install_property (G_OBJECT_CLASS (klass),
266
      PROP_LOOSE_MID_SIDE_STEREO, g_param_spec_boolean ("loose-mid-side-stereo",
267
          "Loose mid side stereo", "Loose mid side stereo",
268
          flacenc_params[DEFAULT_QUALITY].loose_mid_side,
269
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
270
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_BLOCKSIZE,
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
271
      g_param_spec_uint ("blocksize", "Blocksize", "Blocksize in samples",
272
          FLAC__MIN_BLOCK_SIZE, FLAC__MAX_BLOCK_SIZE,
273
          flacenc_params[DEFAULT_QUALITY].blocksize,
274
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
275
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_MAX_LPC_ORDER,
276
      g_param_spec_uint ("max-lpc-order", "Max LPC order",
277
278
          "Max LPC order; 0 => use only fixed predictors", 0,
          FLAC__MAX_LPC_ORDER, flacenc_params[DEFAULT_QUALITY].max_lpc_order,
279
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
280
  g_object_class_install_property (G_OBJECT_CLASS (klass),
281
      PROP_QLP_COEFF_PRECISION, g_param_spec_uint ("qlp-coeff-precision",
282
283
284
          "QLP coefficients precision",
          "Precision in bits of quantized linear-predictor coefficients; 0 = automatic",
          0, 32, flacenc_params[DEFAULT_QUALITY].qlp_coeff_precision,
285
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
286
  g_object_class_install_property (G_OBJECT_CLASS (klass),
287
      PROP_QLP_COEFF_PREC_SEARCH, g_param_spec_boolean ("qlp-coeff-prec-search",
288
289
290
291
          "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,
292
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
293
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_ESCAPE_CODING,
294
      g_param_spec_boolean ("escape-coding", "Do Escape coding",
295
296
          "search for escape codes in the entropy coding stage "
          "for slightly better compression",
297
          flacenc_params[DEFAULT_QUALITY].escape_coding,
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_EXHAUSTIVE_MODEL_SEARCH,
301
      g_param_spec_boolean ("exhaustive-model-search",
302
303
304
          "Do exhaustive model search",
          "do exhaustive search of LP coefficient quantization (expensive!)",
          flacenc_params[DEFAULT_QUALITY].exhaustive_model_search,
305
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
306
  g_object_class_install_property (G_OBJECT_CLASS (klass),
307
      PROP_MIN_RESIDUAL_PARTITION_ORDER,
308
      g_param_spec_uint ("min-residual-partition-order",
309
310
311
          "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,
312
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
313
  g_object_class_install_property (G_OBJECT_CLASS (klass),
314
      PROP_MAX_RESIDUAL_PARTITION_ORDER,
315
      g_param_spec_uint ("max-residual-partition-order",
316
317
318
          "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,
319
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
320
  g_object_class_install_property (G_OBJECT_CLASS (klass),
321
      PROP_RICE_PARAMETER_SEARCH_DIST,
322
      g_param_spec_uint ("rice-parameter-search-dist",
323
324
325
326
          "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,
327
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
328
329
330
331
332
  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,
333
334
          DEFAULT_PADDING,
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
335
336
337
338
339
340
  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,
341
342
          DEFAULT_SEEKPOINTS,
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
343

344
  gst_element_class_add_static_pad_template (gstelement_class, &src_factory);
345
346
347
348
349
350

  sink_caps = gst_flac_enc_generate_sink_caps ();
  sink_templ = gst_pad_template_new ("sink",
      GST_PAD_SINK, GST_PAD_ALWAYS, sink_caps);
  gst_element_class_add_pad_template (gstelement_class, sink_templ);
  gst_caps_unref (sink_caps);
Wim Taymans's avatar
Wim Taymans committed
351

352
  gst_element_class_set_static_metadata (gstelement_class, "FLAC audio encoder",
Wim Taymans's avatar
Wim Taymans committed
353
354
355
      "Codec/Encoder/Audio",
      "Encodes audio with the FLAC lossless audio encoder",
      "Wim Taymans <wim.taymans@chello.be>");
Wim Taymans's avatar
Wim Taymans committed
356
357
358
359
360
361

  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);
362
  base_class->sink_event = GST_DEBUG_FUNCPTR (gst_flac_enc_sink_event);
363
  base_class->sink_query = GST_DEBUG_FUNCPTR (gst_flac_enc_sink_query);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
364
365
}

Wim Taymans's avatar
Wim Taymans committed
366
static void
Wim Taymans's avatar
Wim Taymans committed
367
gst_flac_enc_init (GstFlacEnc * flacenc)
Wim Taymans's avatar
Wim Taymans committed
368
{
369
  GstAudioEncoder *enc = GST_AUDIO_ENCODER (flacenc);
Wim Taymans's avatar
Wim Taymans committed
370

371
  flacenc->encoder = FLAC__stream_encoder_new ();
372
  gst_flac_enc_update_quality (flacenc, DEFAULT_QUALITY);
373
374
375
376

  /* 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
377
378
379
}

static void
380
gst_flac_enc_finalize (GObject * object)
Wim Taymans's avatar
Wim Taymans committed
381
{
382
  GstFlacEnc *flacenc = GST_FLAC_ENC (object);
Wim Taymans's avatar
Wim Taymans committed
383

384
  FLAC__stream_encoder_delete (flacenc->encoder);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
385

386
  G_OBJECT_CLASS (parent_class)->finalize (object);
Wim Taymans's avatar
Wim Taymans committed
387
388
}

389
390
391
392
393
394
395
396
397
398
399
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;
400
  flacenc->tags = gst_tag_list_new_empty ();
Anton Belka's avatar
Anton Belka committed
401
  flacenc->toc = NULL;
402
403
  flacenc->samples_in = 0;
  flacenc->samples_out = 0;
404
405
406
407
408
409
410
411
412
413

  return TRUE;
}

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

  GST_DEBUG_OBJECT (enc, "stop");
414
  gst_tag_list_unref (flacenc->tags);
415
  flacenc->tags = NULL;
Anton Belka's avatar
Anton Belka committed
416
417
418
  if (flacenc->toc)
    gst_toc_unref (flacenc->toc);
  flacenc->toc = NULL;
419
420
421
422
423
424
425
426
427
428
429
430
431
432
  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]);

Anton Belka's avatar
Anton Belka committed
433
434
435
    if (flacenc->meta[3])
      FLAC__metadata_object_delete (flacenc->meta[3]);

436
437
438
439
440
441
442
    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;

443
  gst_tag_setter_reset_tags (GST_TAG_SETTER (enc));
Anton Belka's avatar
Anton Belka committed
444
  gst_toc_setter_reset (GST_TOC_SETTER (enc));
445

446
447
448
  return TRUE;
}

449
450
451
452
453
static void
add_one_tag (const GstTagList * list, const gchar * tag, gpointer user_data)
{
  GList *comments;
  GList *it;
454
  GstFlacEnc *flacenc = GST_FLAC_ENC (user_data);
455

456
457
458
  /* IMAGE and PREVIEW_IMAGE tags are already written
   * differently, no need to store them inside the
   * vorbiscomments too */
459
460
  if (strcmp (tag, GST_TAG_IMAGE) == 0
      || strcmp (tag, GST_TAG_PREVIEW_IMAGE) == 0)
461
462
    return;

463
464
465
466
467
468
469
470
471
472
473
474
475
476
  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);
}

Anton Belka's avatar
Anton Belka committed
477
478
479
480
481
482
483
484
485
486
static gboolean
add_cuesheet (const GstToc * toc, guint sample_rate,
    FLAC__StreamMetadata * cuesheet)
{
  gint8 track_num = 0;
  gint64 start, stop;
  gchar *isrc = NULL;
  const gchar *is_legal;
  GList *list;
  GstTagList *tags;
487
  GstTocEntry *entry, *subentry = NULL;
Anton Belka's avatar
Anton Belka committed
488
489
490
491
492
493
494
495
496
  FLAC__StreamMetadata_CueSheet *cs;
  FLAC__StreamMetadata_CueSheet_Track *track;

  cs = &cuesheet->data.cue_sheet;
  if (!cs)
    return FALSE;

  /* check if the TOC entries is valid */
  list = gst_toc_get_entries (toc);
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
  entry = list->data;
  if (gst_toc_entry_is_alternative (entry)) {
    list = gst_toc_entry_get_sub_entries (entry);
    while (list) {
      subentry = list->data;
      if (!gst_toc_entry_is_sequence (subentry))
        return FALSE;
      list = g_list_next (list);
    }
    list = gst_toc_entry_get_sub_entries (entry);
  }
  if (gst_toc_entry_is_sequence (entry)) {
    while (list) {
      entry = list->data;
      if (!gst_toc_entry_is_sequence (entry))
        return FALSE;
      list = g_list_next (list);
    }
    list = gst_toc_get_entries (toc);
Anton Belka's avatar
Anton Belka committed
516
517
518
519
520
521
522
523
524
525
526
527
528
529
  }

  /* add tracks in cuesheet */
  while (list) {
    entry = list->data;
    gst_toc_entry_get_start_stop_times (entry, &start, &stop);
    tags = gst_toc_entry_get_tags (entry);
    if (tags)
      gst_tag_list_get_string (tags, GST_TAG_ISRC, &isrc);
    track = FLAC__metadata_object_cuesheet_track_new ();
    track->offset =
        (FLAC__uint64) gst_util_uint64_scale_round (start, sample_rate,
        GST_SECOND);
    track->number = (FLAC__byte) track_num + 1;
530
531
    if (isrc != NULL && strlen (isrc) <= 12)
      g_strlcpy (track->isrc, isrc, 13);
Anton Belka's avatar
Anton Belka committed
532
533
534
535
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
    if (track->number <= 0)
      return FALSE;
    if (!FLAC__metadata_object_cuesheet_insert_track (cuesheet, track_num,
            track, FALSE))
      return FALSE;
    if (!FLAC__metadata_object_cuesheet_track_insert_blank_index (cuesheet,
            track_num, 0))
      return FALSE;
    track_num++;
    list = g_list_next (list);
  }

  if (cs->num_tracks <= 0)
    return FALSE;

  /* add lead-out track in cuesheet */
  track = FLAC__metadata_object_cuesheet_track_new ();
  track->offset =
      (FLAC__uint64) gst_util_uint64_scale_round (stop, sample_rate,
      GST_SECOND);
  track->number = 255;
  if (!FLAC__metadata_object_cuesheet_insert_track (cuesheet, cs->num_tracks,
          track, FALSE))
    return FALSE;

  /* check if the cuesheet is valid */
  if (!FLAC__metadata_object_cuesheet_is_legal (cuesheet, FALSE, &is_legal)) {
    g_warning ("%s\n", is_legal);
    return FALSE;
  }

  return TRUE;
}

566
static void
Anton Belka's avatar
Anton Belka committed
567
568
gst_flac_enc_set_metadata (GstFlacEnc * flacenc, GstAudioInfo * info,
    guint64 total_samples)
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
569
{
570
571
  const GstTagList *user_tags;
  GstTagList *copy;
572
  gint entries = 1;
573
  gint n_images, n_preview_images;
Anton Belka's avatar
Anton Belka committed
574
  FLAC__StreamMetadata *cuesheet;
575
576

  g_return_if_fail (flacenc != NULL);
Anton Belka's avatar
Anton Belka committed
577

578
  user_tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (flacenc));
579
580
581
582
  if ((flacenc->tags == NULL) && (user_tags == NULL)) {
    return;
  }
  copy = gst_tag_list_merge (user_tags, flacenc->tags,
583
      gst_tag_setter_get_tag_merge_mode (GST_TAG_SETTER (flacenc)));
584
585
586
587
  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 =
Anton Belka's avatar
Anton Belka committed
588
      g_new0 (FLAC__StreamMetadata *, 4 + n_images + n_preview_images);
589
590
591
592
593

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

Anton Belka's avatar
Anton Belka committed
594
595
596
597
598
599
600
601
602
603
604
605
606
607
  if (!flacenc->toc)
    flacenc->toc = gst_toc_setter_get_toc (GST_TOC_SETTER (flacenc));

  if (flacenc->toc) {
    cuesheet = FLAC__metadata_object_new (FLAC__METADATA_TYPE_CUESHEET);
    if (add_cuesheet (flacenc->toc, GST_AUDIO_INFO_RATE (info), cuesheet)) {
      flacenc->meta[entries] = cuesheet;
      entries++;
    } else {
      FLAC__metadata_object_delete (cuesheet);
      flacenc->meta[entries] = NULL;
    }
  }

608
  if (n_images + n_preview_images > 0) {
609
    GstSample *sample;
610
    GstBuffer *buffer;
611
    GstCaps *caps;
612
    const GstStructure *structure;
613
    GstTagImageType image_type = GST_TAG_IMAGE_TYPE_NONE;
614
    gint i, width = 0, height = 0, png_icon_count = 0, other_icon_count = 0;
Wim Taymans's avatar
Wim Taymans committed
615
    GstMapInfo map;
616
617

    for (i = 0; i < n_images + n_preview_images; i++) {
618
619
      gboolean is_preview_image = (i >= n_images);

620
      if (i < n_images) {
621
        if (!gst_tag_list_get_sample_index (copy, GST_TAG_IMAGE, i, &sample))
622
623
          continue;
      } else {
624
625
        if (!gst_tag_list_get_sample_index (copy, GST_TAG_PREVIEW_IMAGE,
                i - n_images, &sample))
626
627
628
          continue;
      }

629
      structure = gst_sample_get_info (sample);
630
631
632
633
      caps = gst_sample_get_caps (sample);
      if (!caps) {
        GST_FIXME_OBJECT (flacenc, "Image tag without caps");
        gst_sample_unref (sample);
634
635
636
        continue;
      }

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

640
641
      GST_LOG_OBJECT (flacenc, "image info: %" GST_PTR_FORMAT, structure);

642
643
644
645
646
647
      if (structure)
        gst_structure_get (structure, "image-type", GST_TYPE_TAG_IMAGE_TYPE,
            &image_type, NULL);
      else
        image_type = GST_TAG_IMAGE_TYPE_NONE;

648
649
650
651
652
653
      GST_LOG_OBJECT (flacenc, "image caps: %" GST_PTR_FORMAT, caps);

      structure = gst_caps_get_structure (caps, 0);
      gst_structure_get (structure, "width", G_TYPE_INT, &width,
          "height", G_TYPE_INT, &height, NULL);

654
      /* Convert to ID3v2 APIC image type */
655
656
657
658
659
660
661
662
663
      if (image_type == GST_TAG_IMAGE_TYPE_NONE) {
        if (is_preview_image) {
          /* 1 - 32x32 pixels 'file icon' (PNG only)
           * 2 - Other file icon
           * There may only be one each of picture type 1 and 2 in a file. */
          if (width == 32 && height == 32
              && gst_structure_has_name (structure, "image/png")
              && png_icon_count++ == 0) {
            image_type = 1;
664
          } else if (width <= 32 && height <= 32 && other_icon_count++ == 0) {
665
666
667
668
669
670
671
672
673
            image_type = 2;
          } else {
            image_type = 0;     /* Other */
          }
        } else {
          image_type = 0;       /* Other */
        }
      } else {
        /* GStreamer enum is the same but without the two icon types 1+2 */
674
        image_type = image_type + 2;
675
      }
676

677
      buffer = gst_sample_get_buffer (sample);
Wim Taymans's avatar
Wim Taymans committed
678
      gst_buffer_map (buffer, &map, GST_MAP_READ);
679
      FLAC__metadata_object_picture_set_data (flacenc->meta[entries],
Wim Taymans's avatar
Wim Taymans committed
680
681
          map.data, map.size, TRUE);
      gst_buffer_unmap (buffer, &map);
Wim Taymans's avatar
Wim Taymans committed
682

683
      GST_LOG_OBJECT (flacenc, "Setting picture type %d", image_type);
684
      flacenc->meta[entries]->data.picture.type = image_type;
685

686
687
688
689
690
      if (width > 0 && height > 0) {
        flacenc->meta[entries]->data.picture.width = width;
        flacenc->meta[entries]->data.picture.height = height;
      }

691
692
      FLAC__metadata_object_picture_set_mime_type (flacenc->meta[entries],
          (char *) gst_structure_get_name (structure), TRUE);
Wim Taymans's avatar
Wim Taymans committed
693

694
      gst_sample_unref (sample);
695
696
697
698
      entries++;
    }
  }

699
700
701
702
  if (flacenc->seekpoints && total_samples != GST_CLOCK_TIME_NONE) {
    gboolean res;
    guint samples;

703
    flacenc->meta[entries] =
704
705
706
707
        FLAC__metadata_object_new (FLAC__METADATA_TYPE_SEEKTABLE);
    if (flacenc->seekpoints > 0) {
      res =
          FLAC__metadata_object_seektable_template_append_spaced_points
708
          (flacenc->meta[entries], flacenc->seekpoints, total_samples);
709
    } else {
710
      samples = -flacenc->seekpoints * GST_AUDIO_INFO_RATE (info);
711
712
      res =
          FLAC__metadata_object_seektable_template_append_spaced_points_by_samples
713
          (flacenc->meta[entries], samples, total_samples);
714
715
716
717
718
    }
    if (!res) {
      GST_DEBUG_OBJECT (flacenc, "adding seekpoint template %d failed",
          flacenc->seekpoints);
      FLAC__metadata_object_delete (flacenc->meta[1]);
719
      flacenc->meta[entries] = NULL;
720
721
722
723
724
725
726
    } else {
      entries++;
    }
  } else if (flacenc->seekpoints && total_samples == GST_CLOCK_TIME_NONE) {
    GST_WARNING_OBJECT (flacenc, "total time unknown; can not add seekpoints");
  }

727
  if (flacenc->padding > 0) {
728
729
730
731
    flacenc->meta[entries] =
        FLAC__metadata_object_new (FLAC__METADATA_TYPE_PADDING);
    flacenc->meta[entries]->length = flacenc->padding;
    entries++;
732
733
  }

734
  if (FLAC__stream_encoder_set_metadata (flacenc->encoder,
735
          flacenc->meta, entries) != true)
736
    g_warning ("Dude, i'm already initialized!");
737

738
  gst_tag_list_unref (copy);
739
740
}

741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
static GstCaps *
gst_flac_enc_generate_sink_caps (void)
{
  GstCaps *ret;
  gint i;
  GValue v_list = { 0, };
  GValue v = { 0, };
  GstStructure *s, *s2;

  g_value_init (&v_list, GST_TYPE_LIST);
  g_value_init (&v, G_TYPE_STRING);

  /* Use system's endianness */
  g_value_set_static_string (&v, "S8");
  gst_value_list_append_value (&v_list, &v);
  g_value_set_static_string (&v, GST_AUDIO_NE (S16));
  gst_value_list_append_value (&v_list, &v);
  g_value_set_static_string (&v, GST_AUDIO_NE (S24));
  gst_value_list_append_value (&v_list, &v);
  g_value_set_static_string (&v, GST_AUDIO_NE (S24_32));
  gst_value_list_append_value (&v_list, &v);
  g_value_unset (&v);

  s = gst_structure_new_empty ("audio/x-raw");
  gst_structure_take_value (s, "format", &v_list);

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

  ret = gst_caps_new_empty ();
771
772
773
774
775
  s2 = gst_structure_copy (s);
  gst_structure_set (s2, "channels", G_TYPE_INT, 1, NULL);
  gst_caps_append_structure (ret, s2);
  for (i = 2; i <= 8; i++) {
    guint64 channel_mask;
776

777
778
779
780
781
    s2 = gst_structure_copy (s);
    gst_audio_channel_positions_to_mask (channel_positions[i - 1], i,
        FALSE, &channel_mask);
    gst_structure_set (s2, "channels", G_TYPE_INT, i, "channel-mask",
        GST_TYPE_BITMASK, channel_mask, NULL);
782
783
784
785
786
787
788
789

    gst_caps_append_structure (ret, s2);
  }
  gst_structure_free (s);

  return ret;
}

790
static GstCaps *
Wim Taymans's avatar
Wim Taymans committed
791
gst_flac_enc_getcaps (GstAudioEncoder * enc, GstCaps * filter)
792
{
793
794
795
796
  GstCaps *ret = NULL, *caps = NULL;
  GstPad *pad;

  pad = GST_AUDIO_ENCODER_SINK_PAD (enc);
797

798
799
  ret = gst_pad_get_current_caps (pad);
  if (ret == NULL) {
800
    ret = gst_pad_get_pad_template_caps (pad);
801
802
803
804
  }

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

805
  caps = gst_audio_encoder_proxy_getcaps (enc, ret, filter);
806
807
808
  gst_caps_unref (ret);

  return caps;
809
810
}

811
static guint64
812
gst_flac_enc_peer_query_total_samples (GstFlacEnc * flacenc, GstPad * pad)
813
814
{
  gint64 duration;
815
816
  GstAudioInfo *info =
      gst_audio_encoder_get_audio_info (GST_AUDIO_ENCODER (flacenc));
817

818
  GST_DEBUG_OBJECT (flacenc, "querying peer for DEFAULT format duration");
819
  if (gst_pad_peer_query_duration (pad, GST_FORMAT_DEFAULT, &duration)
Wim Taymans's avatar
Wim Taymans committed
820
      && duration != GST_CLOCK_TIME_NONE)
821
822
    goto done;

823
  GST_DEBUG_OBJECT (flacenc, "querying peer for TIME format duration");
824

825
  if (gst_pad_peer_query_duration (pad, GST_FORMAT_TIME, &duration)
Wim Taymans's avatar
Wim Taymans committed
826
      && duration != GST_CLOCK_TIME_NONE) {
827
828
    GST_DEBUG_OBJECT (flacenc, "peer reported duration %" GST_TIME_FORMAT,
        GST_TIME_ARGS (duration));
829
    duration = GST_CLOCK_TIME_TO_FRAMES (duration, GST_AUDIO_INFO_RATE (info));
830
831
832
833
834
835
836
837
838
839
840
841
842
843

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

844
static gboolean
845
gst_flac_enc_set_format (GstAudioEncoder * enc, GstAudioInfo * info)
846
847
{
  GstFlacEnc *flacenc;
848
  guint64 total_samples = GST_CLOCK_TIME_NONE;
849
  FLAC__StreamEncoderInitStatus init_status;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
850

851
  flacenc = GST_FLAC_ENC (enc);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
852

853
  /* if configured again, means something changed, can't handle that */
854
855
  if (FLAC__stream_encoder_get_state (flacenc->encoder) !=
      FLAC__STREAM_ENCODER_UNINITIALIZED)
856
857
    goto encoder_already_initialized;

858
  /* delay setting output caps/format until we have all headers */
859

860
861
862
863
  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);

864
  total_samples = gst_flac_enc_peer_query_total_samples (flacenc,
865
      GST_AUDIO_ENCODER_SINK_PAD (enc));
866

867
  FLAC__stream_encoder_set_bits_per_sample (flacenc->encoder,
868
      GST_AUDIO_INFO_DEPTH (info));
869
870
871
872
  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));
873
874
875

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

Anton Belka's avatar
Anton Belka committed
878
  gst_flac_enc_set_metadata (flacenc, info, total_samples);
879

880
881
882
883
  /* callbacks clear to go now;
   * write callbacks receives headers during init */
  flacenc->stopped = FALSE;

884
885
886
887
888
  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;
889

890
  /* no special feedback to base class; should provide all available samples */
891
892
893
894
895
896
897
898
899
900
901

  return TRUE;

encoder_already_initialized:
  {
    g_warning ("flac already initialized -- fixme allow this");
    return FALSE;
  }
failed_to_initialize:
  {
    GST_ELEMENT_ERROR (flacenc, LIBRARY, INIT, (NULL),
Wim Taymans's avatar
Wim Taymans committed
902
        ("could not initialize encoder (wrong parameters?) %d", init_status));
903
904
    return FALSE;
  }
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
905
906
}

Wim Taymans's avatar
Wim Taymans committed
907
static gboolean
908
gst_flac_enc_update_quality (GstFlacEnc * flacenc, gint quality)
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
909
{
910
911
912
  GstAudioInfo *info =
      gst_audio_encoder_get_audio_info (GST_AUDIO_ENCODER (flacenc));

Wim Taymans's avatar
Wim Taymans committed
913
914
  flacenc->quality = quality;

915
916
917
918
919
920
921
922
923
924
#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
925
926
  g_object_freeze_notify (G_OBJECT (flacenc));

927
928
  if (GST_AUDIO_INFO_CHANNELS (info) == 2
      || GST_AUDIO_INFO_CHANNELS (info) == 0) {
929
930
931
932
    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
933
934
935
936
937
938
939
940
941
942
943
944
945
946
  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
947
948
949
950
951
952

#undef DO_UPDATE

  g_object_thaw_notify (G_OBJECT (flacenc));

  return TRUE;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
953
954
}

955
956
957
static FLAC__StreamEncoderSeekStatus
gst_flac_enc_seek_callback (const FLAC__StreamEncoder * encoder,
    FLAC__uint64 absolute_byte_offset, void *client_data)
958
{
959
  GstFlacEnc *flacenc;
960
  GstPad *peerpad;
Wim Taymans's avatar
Wim Taymans committed
961
  GstSegment seg;
962

963
  flacenc = GST_FLAC_ENC (client_data);
964

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
965
  if (flacenc->stopped)
966
    return FLAC__STREAM_ENCODER_SEEK_STATUS_OK;
967

968
  if ((peerpad = gst_pad_get_peer (GST_AUDIO_ENCODER_SRC_PAD (flacenc)))) {
Wim Taymans's avatar
Wim Taymans committed
969
970
    GstEvent *event;
    gboolean ret;
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
    GstQuery *query;
    gboolean seekable = FALSE;

    /* try to seek to the beginning of the output */
    query = gst_query_new_seeking (GST_FORMAT_BYTES);
    if (gst_pad_query (peerpad, query)) {
      GstFormat format;

      gst_query_parse_seeking (query, &format, &seekable, NULL, NULL);
      if (format != GST_FORMAT_BYTES)
        seekable = FALSE;
    } else {
      GST_LOG_OBJECT (flacenc, "SEEKING query not handled");
    }
    gst_query_unref (query);

    if (!seekable) {
      GST_DEBUG_OBJECT (flacenc, "downstream not seekable; not rewriting");
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
989
      gst_object_unref (peerpad);
990
991
      return FLAC__STREAM_ENCODER_SEEK_STATUS_UNSUPPORTED;
    }
Wim Taymans's avatar
Wim Taymans committed
992
993
994
995
996
997

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

Wim Taymans's avatar
Wim Taymans committed
999
    ret = gst_pad_send_event (peerpad, event);
1000
    gst_object_unref (peerpad);
1001

1002
    if (ret) {
Josep Torra's avatar
Josep Torra committed
1003
1004
      GST_DEBUG ("Seek to %" G_GUINT64_FORMAT " %s",
          (guint64) absolute_byte_offset, "succeeded");
1005
    } else {
Josep Torra's avatar
Josep Torra committed
1006
1007
      GST_DEBUG ("Seek to %" G_GUINT64_FORMAT " %s",
          (guint64) absolute_byte_offset, "failed");
1008
      return FLAC__STREAM_ENCODER_SEEK_STATUS_UNSUPPORTED;
1009
    }
1010
1011
  } else {
    GST_DEBUG ("Seek to %" G_GUINT64_FORMAT " failed (no peer pad)",
Josep Torra's avatar
Josep Torra committed
1012
        (guint64) absolute_byte_offset);
1013
  }
1014
1015

  flacenc->offset = absolute_byte_offset;
1016
  return FLAC__STREAM_ENCODER_SEEK_STATUS_OK;
1017
1018
}

1019
1020
1021
1022
1023
1024
1025
1026
1027
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
1028
  GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_HEADER);
1029
1030
1031
1032
1033
1034
1035
1036
1037
  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

1038
static GstFlowReturn
1039
1040
1041
1042
1043
1044
1045
1046
gst_flac_enc_process_stream_headers (GstFlacEnc * enc)
{
  GstBuffer *vorbiscomment = NULL;
  GstBuffer *streaminfo = NULL;
  GstBuffer *marker = NULL;
  GValue array = { 0, };
  GstCaps *caps;
  GList *l;
1047
  GstFlowReturn ret = GST_FLOW_OK;
1048
1049
  GstAudioInfo *info =
      gst_audio_encoder_get_audio_info (GST_AUDIO_ENCODER (enc));
1050
1051

  caps = gst_caps_new_simple ("audio/x-flac",
1052
1053
      "channels", G_TYPE_INT, GST_AUDIO_INFO_CHANNELS (info),
      "rate", G_TYPE_INT, GST_AUDIO_INFO_RATE (info), NULL);
1054
1055

  for (l = enc->headers; l != NULL; l = l->next) {
Wim Taymans's avatar
Wim Taymans committed
1056
    GstBuffer *buf;
Wim Taymans's avatar
Wim Taymans committed
1057
    GstMapInfo map;
Wim Taymans's avatar
Wim Taymans committed
1058
1059
    guint8 *data;
    gsize size;
1060
1061
1062

    /* 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
1063
    l->data = gst_buffer_make_writable (GST_BUFFER_CAST (l->data));
1064

Wim Taymans's avatar
Wim Taymans committed
1065
    buf = GST_BUFFER_CAST (l->data);
Wim Taymans's avatar
Wim Taymans committed
1066
    GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_HEADER);
Wim Taymans's avatar
Wim Taymans committed
1067

Wim Taymans's avatar
Wim Taymans committed
1068
1069
1070
    gst_buffer_map (buf, &map, GST_MAP_READ);
    data = map.data;
    size = map.size;
1071
1072
1073

    /* 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
1074
      marker = buf;
1075
    } else if (size >