Branch data Line data Source code
1 : : /* GStreamer libsndfile plugin
2 : : * Copyright (C) 2007 Andy Wingo <wingo at pobox dot com>
3 : : *
4 : : * This library is free software; you can redistribute it and/or
5 : : * modify it under the terms of the GNU Library General Public
6 : : * License as published by the Free Software Foundation; either
7 : : * version 2 of the License, or (at your option) any later version.
8 : : *
9 : : * This library is distributed in the hope that it will be useful,
10 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 : : * Library General Public License for more details.
13 : : *
14 : : * You should have received a copy of the GNU Library General Public
15 : : * License along with this library; if not, write to the
16 : : * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 : : * Boston, MA 02111-1307, USA.
18 : : */
19 : :
20 : :
21 : : #ifdef HAVE_CONFIG_H
22 : : #include "config.h"
23 : : #endif
24 : :
25 : : #include <gst/audio/audio.h>
26 : :
27 : : #include <gst/gst-i18n-plugin.h>
28 : :
29 : : #include "gstsfsink.h"
30 : :
31 : : enum
32 : : {
33 : : PROP_0,
34 : : PROP_LOCATION,
35 : : PROP_MAJOR_TYPE,
36 : : PROP_MINOR_TYPE,
37 : : PROP_BUFFER_FRAMES
38 : : };
39 : :
40 : : #define DEFAULT_BUFFER_FRAMES (256)
41 : :
42 : : static GstStaticPadTemplate sf_sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
43 : : GST_PAD_SINK,
44 : : GST_PAD_ALWAYS,
45 : : GST_STATIC_CAPS ("audio/x-raw-float, "
46 : : "rate = (int) [ 1, MAX ], "
47 : : "channels = (int) [ 1, MAX ], "
48 : : "endianness = (int) BYTE_ORDER, "
49 : : "width = (int) 32; "
50 : : "audio/x-raw-int, "
51 : : "rate = (int) [ 1, MAX ], "
52 : : "channels = (int) [ 1, MAX ], "
53 : : "endianness = (int) BYTE_ORDER, "
54 : : "width = (int) {16, 32}, "
55 : : "depth = (int) {16, 32}, " "signed = (boolean) true")
56 : : );
57 : :
58 [ + + ]: 33 : GST_BOILERPLATE (GstSFSink, gst_sf_sink, GstBaseSink, GST_TYPE_BASE_SINK);
59 : :
60 : : static void gst_sf_sink_set_property (GObject * object, guint prop_id,
61 : : const GValue * value, GParamSpec * pspec);
62 : : static void gst_sf_sink_get_property (GObject * object, guint prop_id,
63 : : GValue * value, GParamSpec * pspec);
64 : :
65 : : static gboolean gst_sf_sink_start (GstBaseSink * bsink);
66 : : static gboolean gst_sf_sink_stop (GstBaseSink * bsink);
67 : : static void gst_sf_sink_fixate (GstBaseSink * bsink, GstCaps * caps);
68 : : static gboolean gst_sf_sink_set_caps (GstBaseSink * bsink, GstCaps * caps);
69 : : static gboolean gst_sf_sink_activate_pull (GstBaseSink * bsink,
70 : : gboolean active);
71 : : static GstFlowReturn gst_sf_sink_render (GstBaseSink * bsink,
72 : : GstBuffer * buffer);
73 : : static gboolean gst_sf_sink_event (GstBaseSink * bsink, GstEvent * event);
74 : :
75 : : static gboolean gst_sf_sink_open_file (GstSFSink * this);
76 : : static void gst_sf_sink_close_file (GstSFSink * this);
77 : :
78 : : GST_DEBUG_CATEGORY_STATIC (gst_sf_debug);
79 : : #define GST_CAT_DEFAULT gst_sf_debug
80 : :
81 : : static void
82 : 6 : gst_sf_sink_base_init (gpointer g_class)
83 : : {
84 : 6 : GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
85 : :
86 [ + - ]: 6 : GST_DEBUG_CATEGORY_INIT (gst_sf_debug, "sfsink", 0, "sfsink element");
87 : 6 : gst_element_class_add_pad_template (element_class,
88 : : gst_static_pad_template_get (&sf_sink_factory));
89 : 6 : gst_element_class_set_details_simple (element_class, "Sndfile sink",
90 : : "Sink/Audio",
91 : : "Write audio streams to disk using libsndfile",
92 : : "Andy Wingo <wingo at pobox dot com>");
93 : 6 : }
94 : :
95 : : static void
96 : 6 : gst_sf_sink_class_init (GstSFSinkClass * klass)
97 : : {
98 : : GObjectClass *gobject_class;
99 : : GstBaseSinkClass *basesink_class;
100 : : GParamSpec *pspec;
101 : :
102 : 6 : gobject_class = (GObjectClass *) klass;
103 : 6 : basesink_class = (GstBaseSinkClass *) klass;
104 : :
105 : 6 : gobject_class->set_property = gst_sf_sink_set_property;
106 : 6 : gobject_class->get_property = gst_sf_sink_get_property;
107 : :
108 : 6 : g_object_class_install_property (gobject_class, PROP_LOCATION,
109 : : g_param_spec_string ("location", "File Location",
110 : : "Location of the file to write", NULL,
111 : : G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
112 : 6 : pspec = g_param_spec_enum
113 : 6 : ("major-type", "Major type", "Major output type", GST_TYPE_SF_MAJOR_TYPES,
114 : : SF_FORMAT_WAV,
115 : : G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
116 : 6 : g_object_class_install_property (gobject_class, PROP_MAJOR_TYPE, pspec);
117 : 6 : pspec = g_param_spec_enum
118 : 6 : ("minor-type", "Minor type", "Minor output type", GST_TYPE_SF_MINOR_TYPES,
119 : : SF_FORMAT_FLOAT,
120 : : G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
121 : 6 : g_object_class_install_property (gobject_class, PROP_MINOR_TYPE, pspec);
122 : 6 : pspec = g_param_spec_int
123 : : ("buffer-frames", "Buffer frames",
124 : : "Number of frames per buffer, in pull mode", 1, G_MAXINT,
125 : : DEFAULT_BUFFER_FRAMES,
126 : : G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
127 : 6 : g_object_class_install_property (gobject_class, PROP_BUFFER_FRAMES, pspec);
128 : :
129 : 6 : basesink_class->get_times = NULL;
130 : 6 : basesink_class->start = GST_DEBUG_FUNCPTR (gst_sf_sink_start);
131 : 6 : basesink_class->stop = GST_DEBUG_FUNCPTR (gst_sf_sink_stop);
132 : 6 : basesink_class->fixate = GST_DEBUG_FUNCPTR (gst_sf_sink_fixate);
133 : 6 : basesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_sf_sink_set_caps);
134 : 6 : basesink_class->activate_pull = GST_DEBUG_FUNCPTR (gst_sf_sink_activate_pull);
135 : 6 : basesink_class->render = GST_DEBUG_FUNCPTR (gst_sf_sink_render);
136 : 6 : basesink_class->event = GST_DEBUG_FUNCPTR (gst_sf_sink_event);
137 : 6 : }
138 : :
139 : : static void
140 : 4 : gst_sf_sink_init (GstSFSink * this, GstSFSinkClass * klass)
141 : : {
142 : 4 : GST_BASE_SINK (this)->can_activate_pull = TRUE;
143 : 4 : }
144 : :
145 : : static void
146 : 0 : gst_sf_sink_set_location (GstSFSink * this, const gchar * location)
147 : : {
148 [ # # ]: 0 : if (this->file)
149 : 0 : goto was_open;
150 : :
151 [ # # ]: 0 : if (this->location)
152 : 0 : g_free (this->location);
153 : :
154 [ # # ]: 0 : this->location = location ? g_strdup (location) : NULL;
155 : :
156 : 0 : return;
157 : :
158 : : was_open:
159 : : {
160 : 0 : g_warning ("Changing the `location' property on sfsink when "
161 : : "a file is open not supported.");
162 : 0 : return;
163 : : }
164 : : }
165 : :
166 : :
167 : : static void
168 : 12 : gst_sf_sink_set_property (GObject * object, guint prop_id, const GValue * value,
169 : : GParamSpec * pspec)
170 : : {
171 : 12 : GstSFSink *this = GST_SF_SINK (object);
172 : :
173 [ - + + + : 12 : switch (prop_id) {
- ]
174 : : case PROP_LOCATION:
175 : 0 : gst_sf_sink_set_location (this, g_value_get_string (value));
176 : 0 : break;
177 : :
178 : : case PROP_MAJOR_TYPE:
179 : 4 : this->format_major = g_value_get_enum (value);
180 : 4 : break;
181 : :
182 : : case PROP_MINOR_TYPE:
183 : 4 : this->format_subtype = g_value_get_enum (value);
184 : 4 : break;
185 : :
186 : : case PROP_BUFFER_FRAMES:
187 : 4 : this->buffer_frames = g_value_get_int (value);
188 : 4 : break;
189 : :
190 : : default:
191 : 0 : break;
192 : : }
193 : 12 : }
194 : :
195 : : static void
196 : 4 : gst_sf_sink_get_property (GObject * object, guint prop_id, GValue * value,
197 : : GParamSpec * pspec)
198 : : {
199 : 4 : GstSFSink *this = GST_SF_SINK (object);
200 : :
201 [ + + + + : 4 : switch (prop_id) {
- ]
202 : : case PROP_LOCATION:
203 : 1 : g_value_set_string (value, this->location);
204 : 1 : break;
205 : :
206 : : case PROP_MAJOR_TYPE:
207 : 1 : g_value_set_enum (value, this->format_major);
208 : 1 : break;
209 : :
210 : : case PROP_MINOR_TYPE:
211 : 1 : g_value_set_enum (value, this->format_subtype);
212 : 1 : break;
213 : :
214 : : case PROP_BUFFER_FRAMES:
215 : 1 : g_value_set_int (value, this->buffer_frames);
216 : 1 : break;
217 : :
218 : : default:
219 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
220 : 0 : break;
221 : : }
222 : 4 : }
223 : :
224 : : static gboolean
225 : 4 : gst_sf_sink_start (GstBaseSink * bsink)
226 : : {
227 : : /* pass */
228 : 4 : return TRUE;
229 : : }
230 : :
231 : : static gboolean
232 : 4 : gst_sf_sink_stop (GstBaseSink * bsink)
233 : : {
234 : 4 : GstSFSink *this = GST_SF_SINK (bsink);
235 : :
236 [ - + ]: 4 : if (this->file)
237 : 0 : gst_sf_sink_close_file (this);
238 : :
239 : 4 : return TRUE;
240 : : }
241 : :
242 : : static gboolean
243 : 0 : gst_sf_sink_open_file (GstSFSink * this)
244 : : {
245 : : int mode;
246 : : SF_INFO info;
247 : :
248 [ # # ]: 0 : g_return_val_if_fail (this->file == NULL, FALSE);
249 [ # # ]: 0 : g_return_val_if_fail (this->rate > 0, FALSE);
250 [ # # ]: 0 : g_return_val_if_fail (this->channels > 0, FALSE);
251 : :
252 [ # # ]: 0 : if (!this->location)
253 : 0 : goto no_filename;
254 : :
255 : 0 : mode = SFM_WRITE;
256 : 0 : this->format = this->format_major | this->format_subtype;
257 : 0 : info.samplerate = this->rate;
258 : 0 : info.channels = this->channels;
259 : 0 : info.format = this->format;
260 : :
261 [ # # ]: 0 : GST_INFO_OBJECT (this, "Opening %s with rate %d, %d channels, format 0x%x",
262 : : this->location, info.samplerate, info.channels, info.format);
263 : :
264 [ # # ]: 0 : if (!sf_format_check (&info))
265 : 0 : goto bad_format;
266 : :
267 : 0 : this->file = sf_open (this->location, mode, &info);
268 : :
269 [ # # ]: 0 : if (!this->file)
270 : 0 : goto open_failed;
271 : :
272 : 0 : return TRUE;
273 : :
274 : : no_filename:
275 : : {
276 [ # # ][ # # ]: 0 : GST_ELEMENT_ERROR (this, RESOURCE, NOT_FOUND,
[ # # ][ # # ]
277 : : (_("No file name specified for writing.")), (NULL));
278 : 0 : return FALSE;
279 : : }
280 : : bad_format:
281 : : {
282 [ # # ][ # # ]: 0 : GST_ELEMENT_ERROR (this, STREAM, ENCODE, (NULL),
[ # # ][ # # ]
283 : : ("Input parameters (rate:%d, channels:%d, format:0x%x) invalid",
284 : : info.samplerate, info.channels, info.format));
285 : 0 : return FALSE;
286 : : }
287 : : open_failed:
288 : : {
289 [ # # ][ # # ]: 0 : GST_ELEMENT_ERROR (this, RESOURCE, OPEN_WRITE,
[ # # ][ # # ]
290 : : (_("Could not open file \"%s\" for writing."), this->location),
291 : : ("soundfile error: %s", sf_strerror (NULL)));
292 : 0 : return FALSE;
293 : : }
294 : : }
295 : :
296 : : static void
297 : 0 : gst_sf_sink_close_file (GstSFSink * this)
298 : : {
299 : 0 : int err = 0;
300 : :
301 [ # # ]: 0 : g_return_if_fail (this->file != NULL);
302 : :
303 [ # # ]: 0 : GST_INFO_OBJECT (this, "Closing file %s", this->location);
304 : :
305 [ # # ]: 0 : if ((err = sf_close (this->file)))
306 : 0 : goto close_failed;
307 : :
308 : 0 : this->file = NULL;
309 : :
310 : 0 : return;
311 : :
312 : : close_failed:
313 : : {
314 [ # # ][ # # ]: 0 : GST_ELEMENT_ERROR (this, RESOURCE, CLOSE,
[ # # ][ # # ]
315 : : ("Could not close file file \"%s\".", this->location),
316 : : ("soundfile error: %s", sf_error_number (err)));
317 : 0 : return;
318 : : }
319 : : }
320 : :
321 : : static void
322 : 0 : gst_sf_sink_fixate (GstBaseSink * bsink, GstCaps * caps)
323 : : {
324 : : GstStructure *s;
325 : : gint width, depth;
326 : :
327 : 0 : s = gst_caps_get_structure (caps, 0);
328 : :
329 : : /* fields for all formats */
330 : 0 : gst_structure_fixate_field_nearest_int (s, "rate", 44100);
331 : 0 : gst_structure_fixate_field_nearest_int (s, "channels", 2);
332 : 0 : gst_structure_fixate_field_nearest_int (s, "width", 16);
333 : :
334 : : /* fields for int */
335 [ # # ]: 0 : if (gst_structure_has_field (s, "depth")) {
336 : 0 : gst_structure_get_int (s, "width", &width);
337 : : /* round width to nearest multiple of 8 for the depth */
338 : 0 : depth = GST_ROUND_UP_8 (width);
339 : 0 : gst_structure_fixate_field_nearest_int (s, "depth", depth);
340 : : }
341 [ # # ]: 0 : if (gst_structure_has_field (s, "signed"))
342 : 0 : gst_structure_fixate_field_boolean (s, "signed", TRUE);
343 [ # # ]: 0 : if (gst_structure_has_field (s, "endianness"))
344 : 0 : gst_structure_fixate_field_nearest_int (s, "endianness", G_BYTE_ORDER);
345 : 0 : }
346 : :
347 : : static gboolean
348 : 0 : gst_sf_sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
349 : : {
350 : 0 : GstSFSink *this = (GstSFSink *) bsink;
351 : : GstStructure *structure;
352 : : gint width, channels, rate;
353 : :
354 : 0 : structure = gst_caps_get_structure (caps, 0);
355 : :
356 [ # # ]: 0 : if (!gst_structure_get_int (structure, "width", &width)
357 [ # # ]: 0 : || !gst_structure_get_int (structure, "channels", &channels)
358 [ # # ]: 0 : || !gst_structure_get_int (structure, "rate", &rate))
359 : : goto impossible;
360 : :
361 [ # # ]: 0 : if (gst_structure_has_name (structure, "audio/x-raw-int")) {
362 [ # # # ]: 0 : switch (width) {
363 : : case 16:
364 : 0 : this->writer = (GstSFWriter) sf_writef_short;
365 : 0 : break;
366 : : case 32:
367 : 0 : this->writer = (GstSFWriter) sf_writef_int;
368 : 0 : break;
369 : : default:
370 : 0 : goto impossible;
371 : : }
372 : : } else {
373 [ # # ]: 0 : switch (width) {
374 : : case 32:
375 : 0 : this->writer = (GstSFWriter) sf_writef_float;
376 : 0 : break;
377 : : default:
378 : 0 : goto impossible;
379 : : }
380 : : }
381 : :
382 : 0 : this->bytes_per_frame = width * channels / 8;
383 : 0 : this->rate = rate;
384 : 0 : this->channels = channels;
385 : :
386 : 0 : return gst_sf_sink_open_file (this);
387 : :
388 : : impossible:
389 : : {
390 : 0 : g_warning ("something impossible happened");
391 : 0 : return FALSE;
392 : : }
393 : : }
394 : :
395 : : /* with STREAM_LOCK
396 : : */
397 : : static void
398 : 0 : gst_sf_sink_loop (GstPad * pad)
399 : : {
400 : : GstSFSink *this;
401 : : GstBaseSink *basesink;
402 : 0 : GstBuffer *buf = NULL;
403 : : GstFlowReturn result;
404 : :
405 : 0 : this = GST_SF_SINK (gst_pad_get_parent (pad));
406 : 0 : basesink = GST_BASE_SINK (this);
407 : :
408 : 0 : result = gst_pad_pull_range (pad, basesink->offset,
409 : 0 : this->buffer_frames * this->bytes_per_frame, &buf);
410 [ # # ]: 0 : if (G_UNLIKELY (result != GST_FLOW_OK))
411 : 0 : goto paused;
412 : :
413 [ # # ]: 0 : if (G_UNLIKELY (buf == NULL))
414 : 0 : goto no_buffer;
415 : :
416 : 0 : basesink->offset += GST_BUFFER_SIZE (buf);
417 : :
418 : 0 : GST_PAD_PREROLL_LOCK (pad);
419 : 0 : result = gst_sf_sink_render (basesink, buf);
420 : 0 : GST_PAD_PREROLL_UNLOCK (pad);
421 [ # # ]: 0 : if (G_UNLIKELY (result != GST_FLOW_OK))
422 : 0 : goto paused;
423 : :
424 : 0 : gst_object_unref (this);
425 : :
426 : 0 : return;
427 : :
428 : : /* ERRORS */
429 : : paused:
430 : : {
431 [ # # ]: 0 : GST_INFO_OBJECT (basesink, "pausing task, reason %s",
432 : : gst_flow_get_name (result));
433 : 0 : gst_pad_pause_task (pad);
434 : : /* fatal errors and NOT_LINKED cause EOS */
435 [ # # ]: 0 : if (result == GST_FLOW_UNEXPECTED) {
436 : 0 : gst_pad_send_event (pad, gst_event_new_eos ());
437 [ # # ][ # # ]: 0 : } else if (result < GST_FLOW_UNEXPECTED || result == GST_FLOW_NOT_LINKED) {
438 [ # # ][ # # ]: 0 : GST_ELEMENT_ERROR (basesink, STREAM, FAILED,
[ # # ][ # # ]
439 : : (_("Internal data stream error.")),
440 : : ("stream stopped, reason %s", gst_flow_get_name (result)));
441 : 0 : gst_pad_send_event (pad, gst_event_new_eos ());
442 : : }
443 : 0 : gst_object_unref (this);
444 : 0 : return;
445 : : }
446 : : no_buffer:
447 : : {
448 [ # # ]: 0 : GST_INFO_OBJECT (this, "no buffer, pausing");
449 : 0 : result = GST_FLOW_ERROR;
450 : 0 : goto paused;
451 : : }
452 : : }
453 : :
454 : : static gboolean
455 : 0 : gst_sf_sink_activate_pull (GstBaseSink * basesink, gboolean active)
456 : : {
457 : : gboolean result;
458 : :
459 [ # # ]: 0 : if (active) {
460 : : /* start task */
461 : 0 : result = gst_pad_start_task (basesink->sinkpad,
462 : 0 : (GstTaskFunction) gst_sf_sink_loop, basesink->sinkpad);
463 : : } else {
464 : : /* step 2, make sure streaming finishes */
465 : 0 : result = gst_pad_stop_task (basesink->sinkpad);
466 : : }
467 : :
468 : 0 : return result;
469 : : }
470 : :
471 : : static GstFlowReturn
472 : 0 : gst_sf_sink_render (GstBaseSink * bsink, GstBuffer * buffer)
473 : : {
474 : : GstSFSink *this;
475 : : sf_count_t written, num_to_write;
476 : :
477 : 0 : this = (GstSFSink *) bsink;
478 : :
479 [ # # ]: 0 : if (GST_BUFFER_SIZE (buffer) % this->bytes_per_frame)
480 : 0 : goto bad_length;
481 : :
482 : 0 : num_to_write = GST_BUFFER_SIZE (buffer) / this->bytes_per_frame;
483 : :
484 : 0 : written = this->writer (this->file, GST_BUFFER_DATA (buffer), num_to_write);
485 [ # # ]: 0 : if (written != num_to_write)
486 : 0 : goto short_write;
487 : :
488 : 0 : return GST_FLOW_OK;
489 : :
490 : : bad_length:
491 : : {
492 [ # # ][ # # ]: 0 : GST_ELEMENT_ERROR (this, RESOURCE, WRITE,
[ # # ][ # # ]
493 : : (_("Could not write to file \"%s\"."), this->location),
494 : : ("bad buffer size: %u %% %d != 0", GST_BUFFER_SIZE (buffer),
495 : : this->bytes_per_frame));
496 : 0 : return GST_FLOW_ERROR;
497 : : }
498 : : short_write:
499 : : {
500 [ # # ][ # # ]: 0 : GST_ELEMENT_ERROR (this, RESOURCE, WRITE,
[ # # ][ # # ]
501 : : (_("Could not write to file \"%s\"."), this->location),
502 : : ("soundfile error: %s", sf_strerror (this->file)));
503 : 0 : return GST_FLOW_ERROR;
504 : : }
505 : : }
506 : :
507 : : static gboolean
508 : 0 : gst_sf_sink_event (GstBaseSink * bsink, GstEvent * event)
509 : : {
510 : : GstSFSink *this;
511 : : GstEventType type;
512 : :
513 : 0 : this = (GstSFSink *) bsink;
514 : :
515 : 0 : type = GST_EVENT_TYPE (event);
516 : :
517 [ # # ]: 0 : switch (type) {
518 : : case GST_EVENT_EOS:
519 [ # # ]: 0 : if (this->file)
520 : 0 : sf_write_sync (this->file);
521 : 0 : break;
522 : : default:
523 : 0 : break;
524 : : }
525 : :
526 : 0 : return TRUE;
527 : : }
|