mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-30 05:31:15 +00:00
wavenc: use WAVE_FORMAT_EXTENSIBLE for more than 2 channels
https://bugzilla.gnome.org/show_bug.cgi?id=733444
This commit is contained in:
parent
a9d7c1d95e
commit
f8f61237f8
2 changed files with 196 additions and 79 deletions
|
@ -49,37 +49,6 @@
|
||||||
GST_DEBUG_CATEGORY_STATIC (wavenc_debug);
|
GST_DEBUG_CATEGORY_STATIC (wavenc_debug);
|
||||||
#define GST_CAT_DEFAULT wavenc_debug
|
#define GST_CAT_DEFAULT wavenc_debug
|
||||||
|
|
||||||
struct riff_struct
|
|
||||||
{
|
|
||||||
guint8 id[4]; /* RIFF */
|
|
||||||
guint32 len;
|
|
||||||
guint8 wav_id[4]; /* WAVE */
|
|
||||||
};
|
|
||||||
|
|
||||||
struct chunk_struct
|
|
||||||
{
|
|
||||||
guint8 id[4];
|
|
||||||
guint32 len;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct common_struct
|
|
||||||
{
|
|
||||||
guint16 wFormatTag;
|
|
||||||
guint16 wChannels;
|
|
||||||
guint32 dwSamplesPerSec;
|
|
||||||
guint32 dwAvgBytesPerSec;
|
|
||||||
guint16 wBlockAlign;
|
|
||||||
guint16 wBitsPerSample; /* Only for PCM */
|
|
||||||
};
|
|
||||||
|
|
||||||
struct wave_header
|
|
||||||
{
|
|
||||||
struct riff_struct riff;
|
|
||||||
struct chunk_struct format;
|
|
||||||
struct common_struct common;
|
|
||||||
struct chunk_struct data;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
/* Offset Size Description Value
|
/* Offset Size Description Value
|
||||||
|
@ -113,19 +82,10 @@ typedef struct
|
||||||
} GstWavEncLabl, GstWavEncNote;
|
} GstWavEncLabl, GstWavEncNote;
|
||||||
|
|
||||||
/* FIXME: mono doesn't produce correct files it seems, at least mplayer xruns */
|
/* FIXME: mono doesn't produce correct files it seems, at least mplayer xruns */
|
||||||
/* Max. of two channels, more channels need WAVFORMATEX with
|
|
||||||
* channel layout, which we do not support yet */
|
|
||||||
#define SINK_CAPS \
|
#define SINK_CAPS \
|
||||||
"audio/x-raw, " \
|
"audio/x-raw, " \
|
||||||
"rate = (int) [ 1, MAX ], " \
|
"rate = (int) [ 1, MAX ], " \
|
||||||
"channels = (int) 1, " \
|
"channels = (int) [ 1, 65535 ], " \
|
||||||
"format = (string) { S32LE, S24LE, S16LE, U8, F32LE, F64LE }, " \
|
|
||||||
"layout = (string) interleaved" \
|
|
||||||
"; " \
|
|
||||||
"audio/x-raw, " \
|
|
||||||
"rate = (int) [ 1, MAX ], " \
|
|
||||||
"channels = (int) 2, " \
|
|
||||||
"channel-mask = (bitmask) 0x3, " \
|
|
||||||
"format = (string) { S32LE, S24LE, S16LE, U8, F32LE, F64LE }, " \
|
"format = (string) { S32LE, S24LE, S16LE, U8, F32LE, F64LE }, " \
|
||||||
"layout = (string) interleaved" \
|
"layout = (string) interleaved" \
|
||||||
"; " \
|
"; " \
|
||||||
|
@ -202,53 +162,187 @@ gst_wavenc_init (GstWavEnc * wavenc)
|
||||||
gst_element_add_pad (GST_ELEMENT (wavenc), wavenc->srcpad);
|
gst_element_add_pad (GST_ELEMENT (wavenc), wavenc->srcpad);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define WAV_HEADER_LEN 44
|
#define RIFF_CHUNK_LEN 12
|
||||||
|
#define FMT_WAV_CHUNK_LEN 24
|
||||||
|
#define FMT_EXT_CHUNK_LEN 48
|
||||||
|
#define FACT_CHUNK_LEN 12
|
||||||
|
#define DATA_HEADER_LEN 8
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
use_format_ext (GstWavEnc * wavenc)
|
||||||
|
{
|
||||||
|
return wavenc->channels > 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
get_header_len (GstWavEnc * wavenc)
|
||||||
|
{
|
||||||
|
int len = RIFF_CHUNK_LEN;
|
||||||
|
|
||||||
|
if (use_format_ext (wavenc))
|
||||||
|
len += FMT_EXT_CHUNK_LEN + FACT_CHUNK_LEN;
|
||||||
|
else
|
||||||
|
len += FMT_WAV_CHUNK_LEN;
|
||||||
|
|
||||||
|
return len + DATA_HEADER_LEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
static guint64
|
||||||
|
gstmask_to_wavmask (guint64 gstmask, GstAudioChannelPosition * pos)
|
||||||
|
{
|
||||||
|
const GstAudioChannelPosition valid_pos =
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT |
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT |
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER |
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_LFE1 |
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_REAR_LEFT |
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT |
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER |
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER |
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_REAR_CENTER |
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT |
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT |
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_TOP_CENTER |
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_LEFT |
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_CENTER |
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_RIGHT |
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_TOP_REAR_LEFT |
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_TOP_REAR_CENTER |
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_TOP_REAR_RIGHT;
|
||||||
|
|
||||||
|
const GstAudioChannelPosition wav_pos[] = {
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_LFE1,
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT,
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER,
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER,
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_REAR_CENTER,
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT,
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT,
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_TOP_CENTER,
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_LEFT,
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_CENTER,
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_RIGHT,
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_TOP_REAR_LEFT,
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_TOP_REAR_CENTER,
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_TOP_REAR_RIGHT,
|
||||||
|
};
|
||||||
|
int k;
|
||||||
|
int chan = 0;
|
||||||
|
guint64 ret = 0;
|
||||||
|
guint64 mask = 1;
|
||||||
|
|
||||||
|
if (gstmask == 0 || ((gstmask & ~valid_pos) != 0))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
for (k = 0; k < G_N_ELEMENTS (wav_pos); ++k) {
|
||||||
|
if (gstmask & wav_pos[k]) {
|
||||||
|
ret |= mask;
|
||||||
|
pos[chan++] = wav_pos[k];
|
||||||
|
}
|
||||||
|
mask <<= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static guint8 *
|
||||||
|
write_fmt_chunk (GstWavEnc * wavenc, guint8 * header)
|
||||||
|
{
|
||||||
|
guint16 wBlockAlign;
|
||||||
|
|
||||||
|
wBlockAlign = (wavenc->width / 8) * wavenc->channels;
|
||||||
|
|
||||||
|
memcpy (header, "fmt ", 4);
|
||||||
|
/* wChannels */
|
||||||
|
GST_WRITE_UINT16_LE (header + 10, wavenc->channels);
|
||||||
|
/* dwSamplesPerSec */
|
||||||
|
GST_WRITE_UINT32_LE (header + 12, wavenc->rate);
|
||||||
|
/* dwAvgBytesPerSec */
|
||||||
|
GST_WRITE_UINT32_LE (header + 16, wBlockAlign * wavenc->rate);
|
||||||
|
/* wBlockAlign */
|
||||||
|
GST_WRITE_UINT16_LE (header + 20, wBlockAlign);
|
||||||
|
/* wBitsPerSample */
|
||||||
|
GST_WRITE_UINT16_LE (header + 22, wavenc->width);
|
||||||
|
|
||||||
|
if (use_format_ext (wavenc)) {
|
||||||
|
GST_DEBUG_OBJECT (wavenc, "Using WAVE_FORMAT_EXTENSIBLE");
|
||||||
|
|
||||||
|
GST_WRITE_UINT32_LE (header + 4, FMT_EXT_CHUNK_LEN - 8);
|
||||||
|
|
||||||
|
/* wFormatTag */
|
||||||
|
GST_WRITE_UINT16_LE (header + 8, 0xFFFE);
|
||||||
|
/* cbSize */
|
||||||
|
GST_WRITE_UINT16_LE (header + 24, 22);
|
||||||
|
/* wValidBitsPerSample */
|
||||||
|
GST_WRITE_UINT16_LE (header + 26, wavenc->width);
|
||||||
|
/* dwChannelMask */
|
||||||
|
GST_WRITE_UINT32_LE (header + 28, (guint32) wavenc->channel_mask);
|
||||||
|
|
||||||
|
GST_WRITE_UINT16_LE (header + 32, wavenc->format);
|
||||||
|
|
||||||
|
memcpy (header + 34,
|
||||||
|
"\x00\x00\x00\x00\x10\x00\x80\x00\x00\xAA\x00\x38\x9B\x71", 14);
|
||||||
|
|
||||||
|
header += FMT_EXT_CHUNK_LEN;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
GST_WRITE_UINT32_LE (header + 4, FMT_WAV_CHUNK_LEN - 8);
|
||||||
|
|
||||||
|
/* wFormatTag */
|
||||||
|
GST_WRITE_UINT16_LE (header + 8, wavenc->format);
|
||||||
|
header += FMT_WAV_CHUNK_LEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
static guint8 *
|
||||||
|
write_fact_chunk (GstWavEnc * wavenc, guint8 * header)
|
||||||
|
{
|
||||||
|
memcpy (header, "fact", 4);
|
||||||
|
GST_WRITE_UINT32_LE (header + 4, FACT_CHUNK_LEN - 8);
|
||||||
|
/* compressed files are only supported up to 2 channels,
|
||||||
|
* that means we never write a fact chunk for them */
|
||||||
|
GST_WRITE_UINT32_LE (header + 8,
|
||||||
|
wavenc->audio_length / (wavenc->width / 8) / wavenc->channels);
|
||||||
|
return header + FACT_CHUNK_LEN;
|
||||||
|
}
|
||||||
|
|
||||||
static GstBuffer *
|
static GstBuffer *
|
||||||
gst_wavenc_create_header_buf (GstWavEnc * wavenc)
|
gst_wavenc_create_header_buf (GstWavEnc * wavenc)
|
||||||
{
|
{
|
||||||
struct wave_header wave;
|
|
||||||
GstBuffer *buf;
|
GstBuffer *buf;
|
||||||
GstMapInfo map;
|
GstMapInfo map;
|
||||||
guint8 *header;
|
guint8 *header;
|
||||||
|
guint32 riffLen;
|
||||||
|
|
||||||
buf = gst_buffer_new_and_alloc (WAV_HEADER_LEN);
|
GST_DEBUG_OBJECT (wavenc, "Header size: %d", get_header_len (wavenc));
|
||||||
|
buf = gst_buffer_new_and_alloc (get_header_len (wavenc));
|
||||||
gst_buffer_map (buf, &map, GST_MAP_WRITE);
|
gst_buffer_map (buf, &map, GST_MAP_WRITE);
|
||||||
header = map.data;
|
header = map.data;
|
||||||
memset (header, 0, WAV_HEADER_LEN);
|
memset (header, 0, get_header_len (wavenc));
|
||||||
|
|
||||||
memcpy (wave.riff.id, "RIFF", 4);
|
riffLen = wavenc->meta_length + wavenc->audio_length
|
||||||
wave.riff.len =
|
+ get_header_len (wavenc) - 8;
|
||||||
wavenc->meta_length + wavenc->audio_length + WAV_HEADER_LEN - 8;
|
|
||||||
memcpy (wave.riff.wav_id, "WAVE", 4);
|
|
||||||
|
|
||||||
memcpy (wave.format.id, "fmt ", 4);
|
/* RIFF chunk */
|
||||||
wave.format.len = 16;
|
memcpy (header, "RIFF", 4);
|
||||||
|
GST_WRITE_UINT32_LE (header + 4, riffLen);
|
||||||
|
memcpy (header + 8, "WAVE", 4);
|
||||||
|
header += RIFF_CHUNK_LEN;
|
||||||
|
|
||||||
wave.common.wChannels = wavenc->channels;
|
header = write_fmt_chunk (wavenc, header);
|
||||||
wave.common.wBitsPerSample = wavenc->width;
|
if (use_format_ext (wavenc))
|
||||||
wave.common.dwSamplesPerSec = wavenc->rate;
|
header = write_fact_chunk (wavenc, header);
|
||||||
wave.common.wFormatTag = wavenc->format;
|
|
||||||
wave.common.wBlockAlign = (wavenc->width / 8) * wave.common.wChannels;
|
|
||||||
wave.common.dwAvgBytesPerSec =
|
|
||||||
wave.common.wBlockAlign * wave.common.dwSamplesPerSec;
|
|
||||||
|
|
||||||
memcpy (wave.data.id, "data", 4);
|
/* data chunk */
|
||||||
wave.data.len = wavenc->audio_length;
|
memcpy (header, "data ", 4);
|
||||||
|
GST_WRITE_UINT32_LE (header + 4, wavenc->audio_length);
|
||||||
memcpy (header, (char *) wave.riff.id, 4);
|
|
||||||
GST_WRITE_UINT32_LE (header + 4, wave.riff.len);
|
|
||||||
memcpy (header + 8, (char *) wave.riff.wav_id, 4);
|
|
||||||
memcpy (header + 12, (char *) wave.format.id, 4);
|
|
||||||
GST_WRITE_UINT32_LE (header + 16, wave.format.len);
|
|
||||||
GST_WRITE_UINT16_LE (header + 20, wave.common.wFormatTag);
|
|
||||||
GST_WRITE_UINT16_LE (header + 22, wave.common.wChannels);
|
|
||||||
GST_WRITE_UINT32_LE (header + 24, wave.common.dwSamplesPerSec);
|
|
||||||
GST_WRITE_UINT32_LE (header + 28, wave.common.dwAvgBytesPerSec);
|
|
||||||
GST_WRITE_UINT16_LE (header + 32, wave.common.wBlockAlign);
|
|
||||||
GST_WRITE_UINT16_LE (header + 34, wave.common.wBitsPerSample);
|
|
||||||
memcpy (header + 36, (char *) wave.data.id, 4);
|
|
||||||
GST_WRITE_UINT32_LE (header + 40, wave.data.len);
|
|
||||||
|
|
||||||
gst_buffer_unmap (buf, &map);
|
gst_buffer_unmap (buf, &map);
|
||||||
|
|
||||||
|
@ -313,11 +407,26 @@ gst_wavenc_sink_setcaps (GstPad * pad, GstCaps * caps)
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wavenc->channels = chans;
|
||||||
|
wavenc->rate = rate;
|
||||||
|
wavenc->channel_mask = 0;
|
||||||
|
|
||||||
if (strcmp (name, "audio/x-raw") == 0) {
|
if (strcmp (name, "audio/x-raw") == 0) {
|
||||||
GstAudioInfo info;
|
GstAudioInfo info;
|
||||||
|
guint64 gstmask;
|
||||||
|
|
||||||
if (!gst_audio_info_from_caps (&info, caps))
|
if (!gst_audio_info_from_caps (&info, caps)) {
|
||||||
|
GST_WARNING_OBJECT (wavenc, "Could not retrieve audio info from caps");
|
||||||
goto fail;
|
goto fail;
|
||||||
|
}
|
||||||
|
if (gst_audio_channel_positions_to_mask (info.position, wavenc->channels,
|
||||||
|
FALSE, &gstmask)) {
|
||||||
|
wavenc->channel_mask = gstmask_to_wavmask (gstmask, wavenc->destPos);
|
||||||
|
memcpy (wavenc->srcPos, info.position, sizeof (info.position));
|
||||||
|
GST_DEBUG_OBJECT (wavenc, "Channel mask input: %" G_GUINT64_FORMAT
|
||||||
|
" output: %" G_GUINT64_FORMAT, gstmask, wavenc->channel_mask);
|
||||||
|
}
|
||||||
|
wavenc->audio_format = GST_AUDIO_INFO_FORMAT (&info);
|
||||||
|
|
||||||
if (GST_AUDIO_INFO_IS_INTEGER (&info))
|
if (GST_AUDIO_INFO_IS_INTEGER (&info))
|
||||||
wavenc->format = GST_RIFF_WAVE_FORMAT_PCM;
|
wavenc->format = GST_RIFF_WAVE_FORMAT_PCM;
|
||||||
|
@ -338,9 +447,6 @@ gst_wavenc_sink_setcaps (GstPad * pad, GstCaps * caps)
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
wavenc->channels = chans;
|
|
||||||
wavenc->rate = rate;
|
|
||||||
|
|
||||||
GST_LOG_OBJECT (wavenc,
|
GST_LOG_OBJECT (wavenc,
|
||||||
"accepted caps: format=0x%04x chans=%u width=%u rate=%u",
|
"accepted caps: format=0x%04x chans=%u width=%u rate=%u",
|
||||||
wavenc->format, wavenc->channels, wavenc->width, wavenc->rate);
|
wavenc->format, wavenc->channels, wavenc->width, wavenc->rate);
|
||||||
|
@ -875,11 +981,17 @@ gst_wavenc_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
|
||||||
|
|
||||||
buf = gst_buffer_make_writable (buf);
|
buf = gst_buffer_make_writable (buf);
|
||||||
|
|
||||||
GST_BUFFER_OFFSET (buf) = WAV_HEADER_LEN + wavenc->audio_length;
|
GST_BUFFER_OFFSET (buf) = get_header_len (wavenc) + wavenc->audio_length;
|
||||||
GST_BUFFER_OFFSET_END (buf) = GST_BUFFER_OFFSET_NONE;
|
GST_BUFFER_OFFSET_END (buf) = GST_BUFFER_OFFSET_NONE;
|
||||||
|
|
||||||
wavenc->audio_length += gst_buffer_get_size (buf);
|
wavenc->audio_length += gst_buffer_get_size (buf);
|
||||||
|
|
||||||
|
if (wavenc->channel_mask != 0 &&
|
||||||
|
!gst_audio_buffer_reorder_channels (buf, wavenc->audio_format,
|
||||||
|
wavenc->channels, wavenc->srcPos, wavenc->destPos)) {
|
||||||
|
GST_WARNING_OBJECT (wavenc, "Could not reorder channels");
|
||||||
|
}
|
||||||
|
|
||||||
flow = gst_pad_push (wavenc->srcpad, buf);
|
flow = gst_pad_push (wavenc->srcpad, buf);
|
||||||
|
|
||||||
return flow;
|
return flow;
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
|
|
||||||
|
|
||||||
#include <gst/gst.h>
|
#include <gst/gst.h>
|
||||||
|
#include <gst/audio/audio.h>
|
||||||
|
|
||||||
G_BEGIN_DECLS
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
@ -53,10 +54,14 @@ struct _GstWavEnc {
|
||||||
GList *notes;
|
GList *notes;
|
||||||
|
|
||||||
/* useful audio data */
|
/* useful audio data */
|
||||||
|
GstAudioFormat audio_format;
|
||||||
guint16 format;
|
guint16 format;
|
||||||
guint width;
|
guint width;
|
||||||
guint rate;
|
guint rate;
|
||||||
guint channels;
|
guint channels;
|
||||||
|
guint64 channel_mask;
|
||||||
|
GstAudioChannelPosition srcPos[64];
|
||||||
|
GstAudioChannelPosition destPos[64];
|
||||||
|
|
||||||
/* data sizes */
|
/* data sizes */
|
||||||
guint32 audio_length;
|
guint32 audio_length;
|
||||||
|
|
Loading…
Reference in a new issue