gstspeexenc.c 31.7 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* GStreamer
 * 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.
 */


21
22
23
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
24
#include <stdlib.h>
25
#include <string.h>
26
27
#include <time.h>
#include <math.h>
28
29
#include <speex/speex.h>
#include <speex/speex_stereo.h>
30

31
32
#include <gst/gsttaginterface.h>
#include <gst/tag/tag.h>
33
34
#include "gstspeexenc.h"

35
36
37
38
39
GST_DEBUG_CATEGORY (speexenc_debug);
#define GST_CAT_DEFAULT speexenc_debug

static GstPadTemplate *gst_speexenc_src_template, *gst_speexenc_sink_template;

40
/* elementfactory information */
41
42
GstElementDetails speexenc_details = {
  "Speex encoder",
43
  "Codec/Encoder/Audio",
44
45
  "Encodes audio in Speex format",
  "Wim Taymans <wim@fluendo.com>",
46
47
};

48
/* GstSpeexEnc signals and args */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
49
50
enum
{
51
52
53
54
  /* FILL ME */
  LAST_SIGNAL
};

55
56
57
58
59
60
61
62
63
#define DEFAULT_QUALITY		8.0
#define DEFAULT_BITRATE		0
#define DEFAULT_VBR		FALSE
#define DEFAULT_ABR		0
#define DEFAULT_VAD		FALSE
#define DEFAULT_DTX		FALSE
#define DEFAULT_COMPLEXITY	3
#define DEFAULT_NFRAMES		1

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
64
65
enum
{
66
67
68
69
70
71
72
73
74
75
  ARG_0,
  ARG_QUALITY,
  ARG_BITRATE,
  ARG_VBR,
  ARG_ABR,
  ARG_VAD,
  ARG_DTX,
  ARG_COMPLEXITY,
  ARG_NFRAMES,
  ARG_LAST_MESSAGE
76
77
};

78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
static const GstFormat *
gst_speexenc_get_formats (GstPad * pad)
{
  static const GstFormat src_formats[] = {
    GST_FORMAT_BYTES,
    GST_FORMAT_TIME,
    0
  };
  static const GstFormat sink_formats[] = {
    GST_FORMAT_BYTES,
    GST_FORMAT_DEFAULT,
    GST_FORMAT_TIME,
    0
  };

  return (GST_PAD_IS_SRC (pad) ? src_formats : sink_formats);
}

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
96
static void gst_speexenc_base_init (gpointer g_class);
97
static void gst_speexenc_class_init (GstSpeexEncClass * klass);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
98
static void gst_speexenc_init (GstSpeexEnc * speexenc);
99

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
100
static void gst_speexenc_chain (GstPad * pad, GstData * _data);
101
102
103
104
105
106
107
static gboolean gst_speexenc_setup (GstSpeexEnc * speexenc);

static void gst_speexenc_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec);
static void gst_speexenc_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec);
static GstElementStateReturn gst_speexenc_change_state (GstElement * element);
108
109

static GstElementClass *parent_class = NULL;
110
111

/*static guint gst_speexenc_signals[LAST_SIGNAL] = { 0 }; */
112
113
114
115
116
117
118
119
120

GType
gst_speexenc_get_type (void)
{
  static GType speexenc_type = 0;

  if (!speexenc_type) {
    static const GTypeInfo speexenc_info = {
      sizeof (GstSpeexEncClass),
Iain Holmes's avatar
Iain Holmes committed
121
      gst_speexenc_base_init,
122
123
124
125
126
127
128
129
      NULL,
      (GClassInitFunc) gst_speexenc_class_init,
      NULL,
      NULL,
      sizeof (GstSpeexEnc),
      0,
      (GInstanceInitFunc) gst_speexenc_init,
    };
130
131
132
133
134
    static const GInterfaceInfo tag_setter_info = {
      NULL,
      NULL,
      NULL
    };
135

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
136
    speexenc_type =
137
138
        g_type_register_static (GST_TYPE_ELEMENT, "GstSpeexEnc", &speexenc_info,
        0);
139
140
141
142
143

    g_type_add_interface_static (speexenc_type, GST_TYPE_TAG_SETTER,
        &tag_setter_info);

    GST_DEBUG_CATEGORY_INIT (speexenc_debug, "speexenc", 0, "Speex encoder");
144
145
146
147
  }
  return speexenc_type;
}

148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
static GstCaps *
speex_caps_factory (void)
{
  return gst_caps_new_simple ("audio/x-speex", NULL);
}

static GstCaps *
raw_caps_factory (void)
{
  return
      gst_caps_new_simple ("audio/x-raw-int",
      "rate", GST_TYPE_INT_RANGE, 6000, 48000,
      "channels", GST_TYPE_INT_RANGE, 1, 2,
      "endianness", G_TYPE_INT, G_BYTE_ORDER,
      "signed", G_TYPE_BOOLEAN, TRUE,
      "width", G_TYPE_INT, 16, "depth", G_TYPE_INT, 16, NULL);
}
Iain Holmes's avatar
Iain Holmes committed
165
166
167
168
169

static void
gst_speexenc_base_init (gpointer g_class)
{
  GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
170
  GstCaps *raw_caps, *speex_caps;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
171

172
173
  raw_caps = raw_caps_factory ();
  speex_caps = speex_caps_factory ();
Iain Holmes's avatar
Iain Holmes committed
174

175
176
177
178
179
180
181
182
  gst_speexenc_sink_template = gst_pad_template_new ("sink", GST_PAD_SINK,
      GST_PAD_ALWAYS, raw_caps);
  gst_speexenc_src_template = gst_pad_template_new ("src", GST_PAD_SRC,
      GST_PAD_ALWAYS, speex_caps);
  gst_element_class_add_pad_template (element_class,
      gst_speexenc_sink_template);
  gst_element_class_add_pad_template (element_class, gst_speexenc_src_template);
  gst_element_class_set_details (element_class, &speexenc_details);
Iain Holmes's avatar
Iain Holmes committed
183
184
}

185
static void
186
gst_speexenc_class_init (GstSpeexEncClass * klass)
187
188
189
190
{
  GObjectClass *gobject_class;
  GstElementClass *gstelement_class;

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
191
192
  gobject_class = (GObjectClass *) klass;
  gstelement_class = (GstElementClass *) klass;
193

194
195
196
  gobject_class->set_property = gst_speexenc_set_property;
  gobject_class->get_property = gst_speexenc_get_property;

197
198
199
200
201
  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_QUALITY,
      g_param_spec_float ("quality", "Quality", "Encoding quality",
          0.0, 10.0, DEFAULT_QUALITY, G_PARAM_READWRITE));
  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_BITRATE,
      g_param_spec_int ("bitrate", "Encoding Bit-rate",
202
          "Specify an encoding bit-rate (in bps). (0 = automatic)",
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
          0, G_MAXINT, DEFAULT_BITRATE, G_PARAM_READWRITE));
  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_VBR,
      g_param_spec_boolean ("vbr", "VBR",
          "Enable variable bit-rate", DEFAULT_VBR, G_PARAM_READWRITE));
  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_ABR,
      g_param_spec_int ("abr", "ABR",
          "Enable average bit-rate (0 = disabled)",
          0, G_MAXINT, DEFAULT_ABR, G_PARAM_READWRITE));
  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_VAD,
      g_param_spec_boolean ("vad", "VAD",
          "Enable voice activity detection", DEFAULT_VAD, G_PARAM_READWRITE));
  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DTX,
      g_param_spec_boolean ("dtx", "DTX",
          "Enable discontinuous transmission", DEFAULT_DTX, G_PARAM_READWRITE));
  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_COMPLEXITY,
      g_param_spec_int ("complexity", "Complexity",
          "Set encoding complexity",
          0, G_MAXINT, DEFAULT_COMPLEXITY, G_PARAM_READWRITE));
  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_NFRAMES,
      g_param_spec_int ("nframes", "NFrames",
          "Number of frames per buffer",
          0, G_MAXINT, DEFAULT_NFRAMES, G_PARAM_READWRITE));
  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_LAST_MESSAGE,
      g_param_spec_string ("last-message", "last-message",
          "The last status message", NULL, G_PARAM_READABLE));

229
230
  parent_class = g_type_class_ref (GST_TYPE_ELEMENT);

231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
  gstelement_class->change_state = gst_speexenc_change_state;
}

static GstPadLinkReturn
gst_speexenc_sinkconnect (GstPad * pad, const GstCaps * caps)
{
  GstSpeexEnc *speexenc;
  GstStructure *structure;

  speexenc = GST_SPEEXENC (gst_pad_get_parent (pad));
  speexenc->setup = FALSE;

  structure = gst_caps_get_structure (caps, 0);
  gst_structure_get_int (structure, "channels", &speexenc->channels);
  gst_structure_get_int (structure, "rate", &speexenc->rate);

  gst_speexenc_setup (speexenc);

  if (speexenc->setup)
    return GST_PAD_LINK_OK;

  return GST_PAD_LINK_REFUSED;
}

static gboolean
gst_speexenc_convert_src (GstPad * pad, GstFormat src_format, gint64 src_value,
    GstFormat * dest_format, gint64 * dest_value)
{
  gboolean res = TRUE;
  GstSpeexEnc *speexenc;
  gint64 avg;

  speexenc = GST_SPEEXENC (gst_pad_get_parent (pad));

  if (speexenc->samples_in == 0 ||
      speexenc->bytes_out == 0 || speexenc->rate == 0)
    return FALSE;

  avg = (speexenc->bytes_out * speexenc->rate) / (speexenc->samples_in);

  switch (src_format) {
    case GST_FORMAT_BYTES:
      switch (*dest_format) {
        case GST_FORMAT_TIME:
          *dest_value = src_value * GST_SECOND / avg;
          break;
        default:
          res = FALSE;
      }
      break;
    case GST_FORMAT_TIME:
      switch (*dest_format) {
        case GST_FORMAT_BYTES:
          *dest_value = src_value * avg / GST_SECOND;
          break;
        default:
          res = FALSE;
      }
      break;
    default:
      res = FALSE;
  }
  return res;
}

static gboolean
gst_speexenc_convert_sink (GstPad * pad, GstFormat src_format,
    gint64 src_value, GstFormat * dest_format, gint64 * dest_value)
{
  gboolean res = TRUE;
  guint scale = 1;
  gint bytes_per_sample;
  GstSpeexEnc *speexenc;

  speexenc = GST_SPEEXENC (gst_pad_get_parent (pad));

  bytes_per_sample = speexenc->channels * 2;

  switch (src_format) {
    case GST_FORMAT_BYTES:
      switch (*dest_format) {
        case GST_FORMAT_DEFAULT:
          if (bytes_per_sample == 0)
            return FALSE;
          *dest_value = src_value / bytes_per_sample;
          break;
        case GST_FORMAT_TIME:
        {
          gint byterate = bytes_per_sample * speexenc->rate;

          if (byterate == 0)
            return FALSE;
          *dest_value = src_value * GST_SECOND / byterate;
          break;
        }
        default:
          res = FALSE;
      }
      break;
    case GST_FORMAT_DEFAULT:
      switch (*dest_format) {
        case GST_FORMAT_BYTES:
          *dest_value = src_value * bytes_per_sample;
          break;
        case GST_FORMAT_TIME:
          if (speexenc->rate == 0)
            return FALSE;
          *dest_value = src_value * GST_SECOND / speexenc->rate;
          break;
        default:
          res = FALSE;
      }
      break;
    case GST_FORMAT_TIME:
      switch (*dest_format) {
        case GST_FORMAT_BYTES:
          scale = bytes_per_sample;
          /* fallthrough */
        case GST_FORMAT_DEFAULT:
          *dest_value = src_value * scale * speexenc->rate / GST_SECOND;
          break;
        default:
          res = FALSE;
      }
      break;
    default:
      res = FALSE;
  }
  return res;
}

static const GstQueryType *
gst_speexenc_get_query_types (GstPad * pad)
{
  static const GstQueryType gst_speexenc_src_query_types[] = {
    GST_QUERY_TOTAL,
    GST_QUERY_POSITION,
    0
  };

  return gst_speexenc_src_query_types;
372
373
}

374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
static gboolean
gst_speexenc_src_query (GstPad * pad, GstQueryType type,
    GstFormat * format, gint64 * value)
{
  gboolean res = TRUE;
  GstSpeexEnc *speexenc;

  speexenc = GST_SPEEXENC (gst_pad_get_parent (pad));

  switch (type) {
    case GST_QUERY_TOTAL:
    {
      switch (*format) {
        case GST_FORMAT_BYTES:
        case GST_FORMAT_TIME:
        {
          gint64 peer_value;
          const GstFormat *peer_formats;

          res = FALSE;

          peer_formats = gst_pad_get_formats (GST_PAD_PEER (speexenc->sinkpad));

          while (peer_formats && *peer_formats && !res) {

            GstFormat peer_format = *peer_formats;

            /* do the probe */
            if (gst_pad_query (GST_PAD_PEER (speexenc->sinkpad),
                    GST_QUERY_TOTAL, &peer_format, &peer_value)) {
              GstFormat conv_format;

              /* convert to TIME */
              conv_format = GST_FORMAT_TIME;
              res = gst_pad_convert (speexenc->sinkpad,
                  peer_format, peer_value, &conv_format, value);
              /* and to final format */
              res &= gst_pad_convert (pad,
                  GST_FORMAT_TIME, *value, format, value);
            }
            peer_formats++;
          }
          break;
        }
        default:
          res = FALSE;
          break;
      }
      break;
    }
    case GST_QUERY_POSITION:
      switch (*format) {
        default:
        {
          /* we only know about our samples, convert to requested format */
          res = gst_pad_convert (pad,
              GST_FORMAT_BYTES, speexenc->bytes_out, format, value);
          break;
        }
      }
      break;
    default:
      res = FALSE;
      break;
  }
  return res;
}
441
442

static void
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
443
gst_speexenc_init (GstSpeexEnc * speexenc)
444
{
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
445
  speexenc->sinkpad =
446
      gst_pad_new_from_template (gst_speexenc_sink_template, "sink");
447
448
  gst_element_add_pad (GST_ELEMENT (speexenc), speexenc->sinkpad);
  gst_pad_set_chain_function (speexenc->sinkpad, gst_speexenc_chain);
449
  gst_pad_set_link_function (speexenc->sinkpad, gst_speexenc_sinkconnect);
450
451
452
453
  gst_pad_set_convert_function (speexenc->sinkpad,
      GST_DEBUG_FUNCPTR (gst_speexenc_convert_sink));
  gst_pad_set_formats_function (speexenc->sinkpad,
      GST_DEBUG_FUNCPTR (gst_speexenc_get_formats));
454

455
  speexenc->srcpad =
456
457
458
459
460
461
462
463
464
      gst_pad_new_from_template (gst_speexenc_src_template, "src");
  gst_pad_set_query_function (speexenc->srcpad,
      GST_DEBUG_FUNCPTR (gst_speexenc_src_query));
  gst_pad_set_query_type_function (speexenc->srcpad,
      GST_DEBUG_FUNCPTR (gst_speexenc_get_query_types));
  gst_pad_set_convert_function (speexenc->srcpad,
      GST_DEBUG_FUNCPTR (gst_speexenc_convert_src));
  gst_pad_set_formats_function (speexenc->srcpad,
      GST_DEBUG_FUNCPTR (gst_speexenc_get_formats));
465
466
  gst_element_add_pad (GST_ELEMENT (speexenc), speexenc->srcpad);

467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
  speexenc->channels = -1;
  speexenc->rate = -1;

  speexenc->quality = DEFAULT_QUALITY;
  speexenc->bitrate = DEFAULT_BITRATE;
  speexenc->vbr = DEFAULT_VBR;
  speexenc->abr = DEFAULT_ABR;
  speexenc->vad = DEFAULT_VAD;
  speexenc->dtx = DEFAULT_DTX;
  speexenc->complexity = DEFAULT_COMPLEXITY;
  speexenc->nframes = DEFAULT_NFRAMES;

  speexenc->setup = FALSE;
  speexenc->eos = FALSE;
  speexenc->header_sent = FALSE;

  speexenc->tags = gst_tag_list_new ();
  speexenc->adapter = gst_adapter_new ();

  /* we're chained and we can deal with events */
  GST_FLAG_SET (speexenc, GST_ELEMENT_EVENT_AWARE);
488
489
}

490
491
492
493

static gchar *
gst_speexenc_get_tag_value (const GstTagList * list, const gchar * tag,
    int index)
494
{
495
  gchar *speexvalue = NULL;
496

497
498
499
  if (tag == NULL) {
    return NULL;
  }
500

501
502
503
504
505
506
  /* get tag name right */
  if ((strcmp (tag, GST_TAG_TRACK_NUMBER) == 0)
      || (strcmp (tag, GST_TAG_ALBUM_VOLUME_NUMBER) == 0)
      || (strcmp (tag, GST_TAG_TRACK_COUNT) == 0)
      || (strcmp (tag, GST_TAG_ALBUM_VOLUME_COUNT) == 0)) {
    guint track_no;
507

508
509
510
511
512
513
514
    if (!gst_tag_list_get_uint_index (list, tag, index, &track_no))
      g_assert_not_reached ();
    speexvalue = g_strdup_printf ("%u", track_no);
  } else if (strcmp (tag, GST_TAG_DATE) == 0) {
    /* FIXME: how are dates represented in speex files? */
    GDate *date;
    guint u;
515

516
517
518
519
520
521
522
523
524
525
    if (!gst_tag_list_get_uint_index (list, tag, index, &u))
      g_assert_not_reached ();
    date = g_date_new_julian (u);
    speexvalue =
        g_strdup_printf ("%04d-%02d-%02d", (gint) g_date_get_year (date),
        (gint) g_date_get_month (date), (gint) g_date_get_day (date));
    g_date_free (date);
  } else if (gst_tag_get_type (tag) == G_TYPE_STRING) {
    if (!gst_tag_list_get_string_index (list, tag, index, &speexvalue))
      g_assert_not_reached ();
526
527
  }

528
529
530
531
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
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
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
  return speexvalue;
}

/*
 *  Comments will be stored in the Vorbis style.
 *  It is describled in the "Structure" section of
 *  http://www.xiph.org/ogg/vorbis/doc/v-comment.html
 *
 *  The comment header is decoded as follows:
 *  1) [vendor_length] = read an unsigned integer of 32 bits
 *  2) [vendor_string] = read a UTF-8 vector as [vendor_length] octets
 *  3) [user_comment_list_length] = read an unsigned integer of 32 bits
 *  4) iterate [user_comment_list_length] times {
 *     5) [length] = read an unsigned integer of 32 bits
 *     6) this iteration's user comment = read a UTF-8 vector as [length] octets
 *     }
 *  7) [framing_bit] = read a single bit as boolean
 *  8) if ( [framing_bit]  unset or end of packet ) then ERROR
 *  9) done.
 *
 *  If you have troubles, please write to ymnk@jcraft.com.
 */
#define readint(buf, base) (((buf[base+3]<<24) & 0xff000000)| \
		            ((buf[base+2]<<16) & 0xff0000)|   \
		            ((buf[base+1]<< 8) & 0xff00)|     \
		             (buf[base  ]      & 0xff))
#define writeint(buf, base, val) do{ buf[base+3] = ((val)>>24) & 0xff; \
	                             buf[base+2] = ((val)>>16) & 0xff; \
	                             buf[base+1] = ((val)>> 8) & 0xff; \
	                             buf[base  ] =  (val)      & 0xff; \
	                          }while(0)

static void
comment_init (char **comments, int *length, char *vendor_string)
{
  int vendor_length = strlen (vendor_string);
  int user_comment_list_length = 0;
  int len = 4 + vendor_length + 4;
  char *p = (char *) malloc (len);

  if (p == NULL) {
  }
  writeint (p, 0, vendor_length);
  memcpy (p + 4, vendor_string, vendor_length);
  writeint (p, 4 + vendor_length, user_comment_list_length);
  *length = len;
  *comments = p;
}
static void
comment_add (char **comments, int *length, const char *tag, char *val)
{
  char *p = *comments;
  int vendor_length = readint (p, 0);
  int user_comment_list_length = readint (p, 4 + vendor_length);
  int tag_len = (tag ? strlen (tag) : 0);
  int val_len = strlen (val);
  int len = (*length) + 4 + tag_len + val_len;

  p = (char *) realloc (p, len);

  writeint (p, *length, tag_len + val_len);     /* length of comment */
  if (tag)
    memcpy (p + *length + 4, tag, tag_len);     /* comment */
  memcpy (p + *length + 4 + tag_len, val, val_len);     /* comment */
  writeint (p, 4 + vendor_length, user_comment_list_length + 1);

  *comments = p;
  *length = len;
}

#undef readint
#undef writeint

static void
gst_speexenc_metadata_set1 (const GstTagList * list, const gchar * tag,
    gpointer speexenc)
{
  const gchar *speextag = NULL;
  gchar *speexvalue = NULL;
  guint i, count;
  GstSpeexEnc *enc = GST_SPEEXENC (speexenc);

  speextag = gst_tag_to_vorbis_tag (tag);
  if (speextag == NULL) {
    return;
  }

  count = gst_tag_list_get_tag_size (list, tag);
  for (i = 0; i < count; i++) {
    speexvalue = gst_speexenc_get_tag_value (list, tag, i);

    if (speexvalue != NULL) {
      comment_add (&enc->comments, &enc->comment_len, speextag, speexvalue);
    }
  }
}

static void
gst_speexenc_set_metadata (GstSpeexEnc * speexenc)
{
  GstTagList *copy;
  const GstTagList *user_tags;

  user_tags = gst_tag_setter_get_list (GST_TAG_SETTER (speexenc));
  if (!(speexenc->tags || user_tags))
    return;

  comment_init (&speexenc->comments, &speexenc->comment_len,
      "Encoded with GStreamer Speexenc");
  copy =
      gst_tag_list_merge (user_tags, speexenc->tags,
      gst_tag_setter_get_merge_mode (GST_TAG_SETTER (speexenc)));
  gst_tag_list_foreach (copy, gst_speexenc_metadata_set1, speexenc);
  gst_tag_list_free (copy);
}

static gboolean
gst_speexenc_setup (GstSpeexEnc * speexenc)
{
  speexenc->setup = FALSE;

  switch (speexenc->mode) {
    case GST_SPEEXENC_MODE_UWB:
651
      speexenc->speex_mode = (SpeexMode *) & speex_uwb_mode;
652
653
      break;
    case GST_SPEEXENC_MODE_WB:
654
      speexenc->speex_mode = (SpeexMode *) & speex_wb_mode;
655
656
      break;
    case GST_SPEEXENC_MODE_NB:
657
      speexenc->speex_mode = (SpeexMode *) & speex_nb_mode;
658
659
660
661
662
663
664
665
      break;
    case GST_SPEEXENC_MODE_AUTO:
    default:
      break;
  }

  if (speexenc->rate > 25000) {
    if (speexenc->mode == GST_SPEEXENC_MODE_AUTO) {
666
      speexenc->speex_mode = (SpeexMode *) & speex_uwb_mode;
667
668
669
670
671
672
673
674
675
676
    } else {
      if (speexenc->speex_mode != &speex_uwb_mode) {
        speexenc->last_message =
            g_strdup_printf
            ("Warning: suggest to use ultra wide band mode for this rate");
        g_object_notify (G_OBJECT (speexenc), "last_message");
      }
    }
  } else if (speexenc->rate > 12500) {
    if (speexenc->mode == GST_SPEEXENC_MODE_AUTO) {
677
      speexenc->speex_mode = (SpeexMode *) & speex_wb_mode;
678
679
680
681
682
683
684
685
686
687
    } else {
      if (speexenc->speex_mode != &speex_wb_mode) {
        speexenc->last_message =
            g_strdup_printf
            ("Warning: suggest to use wide band mode for this rate");
        g_object_notify (G_OBJECT (speexenc), "last_message");
      }
    }
  } else {
    if (speexenc->mode == GST_SPEEXENC_MODE_AUTO) {
688
      speexenc->speex_mode = (SpeexMode *) & speex_nb_mode;
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
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
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
    } else {
      if (speexenc->speex_mode != &speex_nb_mode) {
        speexenc->last_message =
            g_strdup_printf
            ("Warning: suggest to use narrow band mode for this rate");
        g_object_notify (G_OBJECT (speexenc), "last_message");
      }
    }
  }

  if (speexenc->rate != 8000 && speexenc->rate != 16000
      && speexenc->rate != 32000) {
    speexenc->last_message =
        g_strdup_printf ("Warning: speex is optimized for 8, 16 and 32 KHz");
    g_object_notify (G_OBJECT (speexenc), "last_message");
  }

  speex_init_header (&speexenc->header, speexenc->rate, 1,
      speexenc->speex_mode);
  speexenc->header.frames_per_packet = speexenc->nframes;
  speexenc->header.vbr = speexenc->vbr;
  speexenc->header.nb_channels = speexenc->channels;

  /*Initialize Speex encoder */
  speexenc->state = speex_encoder_init (speexenc->speex_mode);

  speex_encoder_ctl (speexenc->state, SPEEX_GET_FRAME_SIZE,
      &speexenc->frame_size);
  speex_encoder_ctl (speexenc->state, SPEEX_SET_COMPLEXITY,
      &speexenc->complexity);
  speex_encoder_ctl (speexenc->state, SPEEX_SET_SAMPLING_RATE, &speexenc->rate);

  if (speexenc->vbr)
    speex_encoder_ctl (speexenc->state, SPEEX_SET_VBR_QUALITY,
        &speexenc->quality);
  else {
    gint tmp = floor (speexenc->quality);

    speex_encoder_ctl (speexenc->state, SPEEX_SET_QUALITY, &tmp);
  }
  if (speexenc->bitrate) {
    if (speexenc->quality >= 0.0 && speexenc->vbr) {
      speexenc->last_message =
          g_strdup_printf ("Warning: bitrate option is overriding quality");
      g_object_notify (G_OBJECT (speexenc), "last_message");
    }
    speex_encoder_ctl (speexenc->state, SPEEX_SET_BITRATE, &speexenc->bitrate);
  }
  if (speexenc->vbr) {
    gint tmp = 1;

    speex_encoder_ctl (speexenc->state, SPEEX_SET_VBR, &tmp);
  } else if (speexenc->vad) {
    gint tmp = 1;

    speex_encoder_ctl (speexenc->state, SPEEX_SET_VAD, &tmp);
  }

  if (speexenc->dtx) {
    gint tmp = 1;

    speex_encoder_ctl (speexenc->state, SPEEX_SET_DTX, &tmp);
  }

  if (speexenc->dtx && !(speexenc->vbr || speexenc->abr || speexenc->vad)) {
    speexenc->last_message =
        g_strdup_printf ("Warning: dtx is useless without vad, vbr or abr");
    g_object_notify (G_OBJECT (speexenc), "last_message");
  } else if ((speexenc->vbr || speexenc->abr) && (speexenc->vad)) {
    speexenc->last_message =
        g_strdup_printf ("Warning: vad is already implied by vbr or abr");
    g_object_notify (G_OBJECT (speexenc), "last_message");
  }

  if (speexenc->abr) {
    speex_encoder_ctl (speexenc->state, SPEEX_SET_ABR, &speexenc->abr);
  }

  speex_encoder_ctl (speexenc->state, SPEEX_GET_LOOKAHEAD,
      &speexenc->lookahead);

  speexenc->setup = TRUE;

  return TRUE;
}

/* prepare a buffer for transmission */
static GstBuffer *
gst_speexenc_buffer_from_data (GstSpeexEnc * speexenc, guchar * data,
    gint data_len, guint64 granulepos)
{
  GstBuffer *outbuf;

  outbuf = gst_buffer_new_and_alloc (data_len);
  memcpy (GST_BUFFER_DATA (outbuf), data, data_len);
  GST_BUFFER_OFFSET (outbuf) = speexenc->bytes_out;
  GST_BUFFER_OFFSET_END (outbuf) = granulepos;

  GST_DEBUG ("encoded buffer of %d bytes", GST_BUFFER_SIZE (outbuf));
  return outbuf;
}

/* push out the buffer and do internal bookkeeping */
static void
gst_speexenc_push_buffer (GstSpeexEnc * speexenc, GstBuffer * buffer)
{
  speexenc->bytes_out += GST_BUFFER_SIZE (buffer);

  if (GST_PAD_IS_USABLE (speexenc->srcpad)) {
    gst_pad_push (speexenc->srcpad, GST_DATA (buffer));
  } else {
    gst_buffer_unref (buffer);
  }
}

static void
gst_speexenc_set_header_on_caps (GstCaps * caps, GstBuffer * buf1,
    GstBuffer * buf2)
{
  GstStructure *structure = gst_caps_get_structure (caps, 0);
  GValue list = { 0 };
  GValue value = { 0 };

  /* mark buffers */
  GST_BUFFER_FLAG_SET (buf1, GST_BUFFER_IN_CAPS);
  GST_BUFFER_FLAG_SET (buf2, GST_BUFFER_IN_CAPS);

  /* put buffers in a fixed list */
  g_value_init (&list, GST_TYPE_FIXED_LIST);
  g_value_init (&value, GST_TYPE_BUFFER);
  g_value_set_boxed (&value, buf1);
  gst_value_list_append_value (&list, &value);
  g_value_unset (&value);
  g_value_init (&value, GST_TYPE_BUFFER);
  g_value_set_boxed (&value, buf2);
  gst_value_list_append_value (&list, &value);
  gst_structure_set_value (structure, "streamheader", &list);
  g_value_unset (&value);
  g_value_unset (&list);
828
829
830
}

static void
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
831
gst_speexenc_chain (GstPad * pad, GstData * _data)
832
{
833
  GstBuffer *buf = GST_BUFFER (_data);
834
835
836
837
838
839
  GstSpeexEnc *speexenc;

  g_return_if_fail (pad != NULL);
  g_return_if_fail (GST_IS_PAD (pad));
  g_return_if_fail (buf != NULL);

840
  speexenc = GST_SPEEXENC (gst_pad_get_parent (pad));
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
841

842
843
  if (GST_IS_EVENT (buf)) {
    GstEvent *event = GST_EVENT (buf);
844

845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
    switch (GST_EVENT_TYPE (event)) {
      case GST_EVENT_EOS:
        speexenc->eos = TRUE;
        gst_event_unref (event);
        break;
      case GST_EVENT_TAG:
        if (speexenc->tags) {
          gst_tag_list_insert (speexenc->tags, gst_event_tag_get_list (event),
              gst_tag_setter_get_merge_mode (GST_TAG_SETTER (speexenc)));
        } else {
          g_assert_not_reached ();
        }
        gst_pad_event_default (pad, event);
        return;
      default:
        gst_pad_event_default (pad, event);
        return;
    }
  } else {
    if (!speexenc->setup) {
      gst_buffer_unref (buf);
      GST_ELEMENT_ERROR (speexenc, CORE, NEGOTIATION, (NULL),
          ("encoder not initialized (input is not audio?)"));
868
869
870
      return;
    }

871
872
873
874
875
876
877
878
879
880
881
    if (!speexenc->header_sent) {
      /* Speex streams begin with two headers; the initial header (with
         most of the codec setup parameters) which is mandated by the Ogg
         bitstream spec.  The second header holds any comment fields.
         We merely need to make the headers, then pass them to libspeex 
         one at a time; libspeex handles the additional Ogg bitstream 
         constraints */
      GstBuffer *buf1, *buf2;
      GstCaps *caps;
      guchar *data;
      gint data_len;
882

883
      gst_speexenc_set_metadata (speexenc);
884

885
886
887
      /* create header buffer */
      data = speex_header_to_packet (&speexenc->header, &data_len);
      buf1 = gst_speexenc_buffer_from_data (speexenc, data, data_len, 0);
888

889
890
891
892
      /* create comment buffer */
      buf2 =
          gst_speexenc_buffer_from_data (speexenc, speexenc->comments,
          speexenc->comment_len, 0);
893

894
895
896
      /* mark and put on caps */
      caps = gst_pad_get_caps (speexenc->srcpad);
      gst_speexenc_set_header_on_caps (caps, buf1, buf2);
897

898
899
900
      /* negotiate with these caps */
      GST_DEBUG ("here are the caps: %" GST_PTR_FORMAT, caps);
      gst_pad_try_set_caps (speexenc->srcpad, caps);
901

902
903
904
      /* push out buffers */
      gst_speexenc_push_buffer (speexenc, buf1);
      gst_speexenc_push_buffer (speexenc, buf2);
905

906
      speex_bits_init (&speexenc->bits);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
907
      speex_bits_reset (&speexenc->bits);
908

909
      speexenc->header_sent = TRUE;
910
911
    }

912
913
914
    {
      gint frame_size = speexenc->frame_size;
      gint bytes = frame_size * 2 * speexenc->channels;
915

916
917
      /* push buffer to adapter */
      gst_adapter_push (speexenc->adapter, buf);
918

919
920
921
922
923
      while (gst_adapter_available (speexenc->adapter) >= bytes) {
        gint16 *data;
        gint i;
        gint outsize, written;
        GstBuffer *outbuf;
924

925
        data = (gint16 *) gst_adapter_peek (speexenc->adapter, bytes);
926

927
928
929
930
        for (i = 0; i < frame_size * speexenc->channels; i++) {
          speexenc->input[i] = (gfloat) data[i];
        }
        gst_adapter_flush (speexenc->adapter, bytes);
931

932
933
934
935
936
937
938
939
        speexenc->samples_in += frame_size;

        if (speexenc->channels == 2) {
          speex_encode_stereo (speexenc->input, frame_size, &speexenc->bits);
        }
        speex_encode (speexenc->state, speexenc->input, &speexenc->bits);

        speexenc->frameno++;
940

941
942
943
944
945
946
947
948
949
950
951
952
953
954
        if ((speexenc->frameno % speexenc->nframes) != 0)
          continue;

        speex_bits_insert_terminator (&speexenc->bits);
        outsize = speex_bits_nbytes (&speexenc->bits);
        outbuf =
            gst_pad_alloc_buffer (speexenc->srcpad, GST_BUFFER_OFFSET_NONE,
            outsize);
        written =
            speex_bits_write (&speexenc->bits, GST_BUFFER_DATA (outbuf),
            outsize);
        g_assert (written == outsize);
        speex_bits_reset (&speexenc->bits);

955
956
957
958
        GST_BUFFER_TIMESTAMP (outbuf) =
            (speexenc->frameno * frame_size -
            speexenc->lookahead) * GST_SECOND / speexenc->rate;
        GST_BUFFER_DURATION (outbuf) = frame_size * GST_SECOND / speexenc->rate;
959
960
961
962
963
964
        GST_BUFFER_OFFSET (outbuf) = speexenc->bytes_out;
        GST_BUFFER_OFFSET_END (outbuf) =
            speexenc->frameno * frame_size - speexenc->lookahead;

        gst_speexenc_push_buffer (speexenc, outbuf);
      }
965
    }
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
  }

  if (speexenc->eos) {
    /* clean up and exit. */
    gst_pad_push (speexenc->srcpad, GST_DATA (gst_event_new (GST_EVENT_EOS)));
    gst_element_set_eos (GST_ELEMENT (speexenc));
  }
}

static void
gst_speexenc_get_property (GObject * object, guint prop_id, GValue * value,
    GParamSpec * pspec)
{
  GstSpeexEnc *speexenc;

  g_return_if_fail (GST_IS_SPEEXENC (object));

  speexenc = GST_SPEEXENC (object);
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
  switch (prop_id) {
    case ARG_QUALITY:
      g_value_set_float (value, speexenc->quality);
      break;
    case ARG_BITRATE:
      g_value_set_int (value, speexenc->bitrate);
      break;
    case ARG_VBR:
      g_value_set_boolean (value, speexenc->vbr);
      break;
    case ARG_ABR:
      g_value_set_int (value, speexenc->abr);
      break;
    case ARG_VAD:
      g_value_set_boolean (value, speexenc->vad);
      break;
    case ARG_DTX:
      g_value_set_boolean (value, speexenc->dtx);
      break;
    case ARG_COMPLEXITY:
      g_value_set_int (value, speexenc->complexity);
      break;
    case ARG_NFRAMES:
      g_value_set_int (value, speexenc->nframes);
      break;
    case ARG_LAST_MESSAGE:
      g_value_set_string (value, speexenc->last_message);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
1016
  }
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
}

static void
gst_speexenc_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec)
{
  GstSpeexEnc *speexenc;

  g_return_if_fail (GST_IS_SPEEXENC (object));

  speexenc = GST_SPEEXENC (object);
1028

1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
  switch (prop_id) {
    case ARG_QUALITY:
      speexenc->quality = g_value_get_float (value);
      break;
    case ARG_BITRATE:
      speexenc->bitrate = g_value_get_int (value);
      break;
    case ARG_VBR:
      speexenc->vbr = g_value_get_boolean (value);
      break;
    case ARG_ABR:
      speexenc->abr = g_value_get_int (value);
      break;
    case ARG_VAD:
      speexenc->vad = g_value_get_boolean (value);
      break;
    case ARG_DTX:
      speexenc->dtx = g_value_get_boolean (value);
      break;
    case ARG_COMPLEXITY:
      speexenc->complexity = g_value_get_int (value);
      break;
    case ARG_NFRAMES:
      speexenc->nframes = g_value_get_int (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
1057
  }
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
}

static GstElementStateReturn
gst_speexenc_change_state (GstElement * element)
{
  GstSpeexEnc *speexenc = GST_SPEEXENC (element);

  switch (GST_STATE_TRANSITION (element)) {
    case GST_STATE_NULL_TO_READY:
      break;
    case GST_STATE_READY_TO_PAUSED:
      speexenc->eos = FALSE;
      speexenc->frameno = 0;
      speexenc->samples_in = 0;
      break;
    case GST_STATE_PAUSED_TO_PLAYING:
    case GST_STATE_PLAYING_TO_PAUSED:
      break;
    case GST_STATE_PAUSED_TO_READY:
      speexenc->setup = FALSE;
      speexenc->header_sent = FALSE;
      gst_tag_list_free (speexenc->tags);
      speexenc->tags = gst_tag_list_new ();
      break;
    case GST_STATE_READY_TO_NULL:
    default:
      break;
  }

  if (GST_ELEMENT_CLASS (parent_class)->change_state)
    return GST_ELEMENT_CLASS (parent_class)->change_state (element);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
1089

1090
  return GST_STATE_SUCCESS;
1091
}