wavenc: use WAVE_FORMAT_EXTENSIBLE for more than 2 channels

https://bugzilla.gnome.org/show_bug.cgi?id=733444
This commit is contained in:
Peter G. Baum 2014-09-08 14:06:00 +02:00 committed by Sebastian Dröge
parent a9d7c1d95e
commit f8f61237f8
2 changed files with 196 additions and 79 deletions

View file

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

View file

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