mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-11-03 09:01:54 -05:00
pw-cat: Rework encoded audio support to use libavformat to parse frames
This commit is contained in:
parent
580a3d9872
commit
dc161fc6af
2 changed files with 75 additions and 72 deletions
|
|
@ -55,6 +55,7 @@ if get_option('pw-cat').allowed() and sndfile_dep.found()
|
||||||
'pw-midiplay',
|
'pw-midiplay',
|
||||||
'pw-midirecord',
|
'pw-midirecord',
|
||||||
'pw-dsdplay',
|
'pw-dsdplay',
|
||||||
|
'pw-encplay',
|
||||||
]
|
]
|
||||||
|
|
||||||
executable('pw-cat',
|
executable('pw-cat',
|
||||||
|
|
|
||||||
|
|
@ -143,11 +143,13 @@ struct data {
|
||||||
} dsf;
|
} dsf;
|
||||||
|
|
||||||
#ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION
|
#ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION
|
||||||
FILE *encoded_file;
|
struct {
|
||||||
AVFormatContext *fmt_context;
|
AVFormatContext *format_context;
|
||||||
AVStream *astream;
|
AVStream *audio_stream;
|
||||||
AVCodecContext *ctx;
|
AVPacket *packet;
|
||||||
enum AVSampleFormat sfmt;
|
int stream_index;
|
||||||
|
int64_t accumulated_excess_playtime;
|
||||||
|
} encoded;
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -234,22 +236,27 @@ static int sf_playback_fill_f64(struct data *d, void *dest, unsigned int n_frame
|
||||||
#ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION
|
#ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION
|
||||||
static int encoded_playback_fill(struct data *d, void *dest, unsigned int n_frames)
|
static int encoded_playback_fill(struct data *d, void *dest, unsigned int n_frames)
|
||||||
{
|
{
|
||||||
int ret, size = 0;
|
AVPacket *packet = d->encoded.packet;
|
||||||
uint8_t buffer[16384];
|
int ret;
|
||||||
|
|
||||||
ret = fread(buffer, 1, SPA_MIN(n_frames, sizeof(buffer)), d->encoded_file);
|
while (true) {
|
||||||
if (ret > 0) {
|
if ((ret = av_read_frame(d->encoded.format_context, packet) < 0))
|
||||||
memcpy(dest, buffer, ret);
|
break;
|
||||||
size = ret;
|
|
||||||
|
if (packet->stream_index == d->encoded.stream_index)
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return (int)size;
|
|
||||||
|
memcpy(dest, packet->data, packet->size);
|
||||||
|
|
||||||
|
return packet->size;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int avcodec_ctx_to_info(struct data *data, AVCodecContext *ctx, struct spa_audio_info *info)
|
static int av_codec_params_to_audio_info(struct data *data, AVCodecParameters *codec_params, struct spa_audio_info *info)
|
||||||
{
|
{
|
||||||
int32_t profile;
|
int32_t profile;
|
||||||
|
|
||||||
switch (ctx->codec_id) {
|
switch (codec_params->codec_id) {
|
||||||
case AV_CODEC_ID_VORBIS:
|
case AV_CODEC_ID_VORBIS:
|
||||||
info->media_subtype = SPA_MEDIA_SUBTYPE_vorbis;
|
info->media_subtype = SPA_MEDIA_SUBTYPE_vorbis;
|
||||||
info->info.vorbis.rate = data->rate;
|
info->info.vorbis.rate = data->rate;
|
||||||
|
|
@ -273,7 +280,7 @@ static int avcodec_ctx_to_info(struct data *data, AVCodecContext *ctx, struct sp
|
||||||
case AV_CODEC_ID_WMAVOICE:
|
case AV_CODEC_ID_WMAVOICE:
|
||||||
case AV_CODEC_ID_WMALOSSLESS:
|
case AV_CODEC_ID_WMALOSSLESS:
|
||||||
info->media_subtype = SPA_MEDIA_SUBTYPE_wma;
|
info->media_subtype = SPA_MEDIA_SUBTYPE_wma;
|
||||||
switch (ctx->codec_tag) {
|
switch (codec_params->codec_tag) {
|
||||||
/* TODO see if these hex constants can be replaced by named constants from FFmpeg */
|
/* TODO see if these hex constants can be replaced by named constants from FFmpeg */
|
||||||
case 0x161:
|
case 0x161:
|
||||||
profile = SPA_AUDIO_WMA_PROFILE_WMA9;
|
profile = SPA_AUDIO_WMA_PROFILE_WMA9;
|
||||||
|
|
@ -297,7 +304,7 @@ static int avcodec_ctx_to_info(struct data *data, AVCodecContext *ctx, struct sp
|
||||||
info->info.wma.rate = data->rate;
|
info->info.wma.rate = data->rate;
|
||||||
info->info.wma.channels = data->channels;
|
info->info.wma.channels = data->channels;
|
||||||
info->info.wma.bitrate = data->bitrate;
|
info->info.wma.bitrate = data->bitrate;
|
||||||
info->info.wma.block_align = ctx->block_align;
|
info->info.wma.block_align = codec_params->block_align;
|
||||||
info->info.wma.profile = profile;
|
info->info.wma.profile = profile;
|
||||||
break;
|
break;
|
||||||
case AV_CODEC_ID_FLAC:
|
case AV_CODEC_ID_FLAC:
|
||||||
|
|
@ -937,7 +944,7 @@ static void show_usage(const char *name, bool is_error)
|
||||||
" -m, --midi Midi mode\n"
|
" -m, --midi Midi mode\n"
|
||||||
" -d, --dsd DSD mode\n"
|
" -d, --dsd DSD mode\n"
|
||||||
#ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION
|
#ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION
|
||||||
" -o, --encoded Encoded mode\n"
|
" -o, --encoded Encoded mode\n"
|
||||||
#endif
|
#endif
|
||||||
"\n"), fp);
|
"\n"), fp);
|
||||||
}
|
}
|
||||||
|
|
@ -1232,6 +1239,8 @@ static int setup_encodedfile(struct data *data)
|
||||||
int ret;
|
int ret;
|
||||||
int bits_per_sample;
|
int bits_per_sample;
|
||||||
int num_channels;
|
int num_channels;
|
||||||
|
unsigned int stream_index;
|
||||||
|
const AVCodecParameters *codecpar;
|
||||||
char path[256] = { 0 };
|
char path[256] = { 0 };
|
||||||
|
|
||||||
/* We do not support record with encoded media */
|
/* We do not support record with encoded media */
|
||||||
|
|
@ -1242,72 +1251,63 @@ static int setup_encodedfile(struct data *data)
|
||||||
strcpy(path, "file:");
|
strcpy(path, "file:");
|
||||||
strcat(path, data->filename);
|
strcat(path, data->filename);
|
||||||
|
|
||||||
data->fmt_context = NULL;
|
data->encoded.format_context = NULL;
|
||||||
ret = avformat_open_input(&data->fmt_context, path, NULL, NULL);
|
if ((ret = avformat_open_input(&data->encoded.format_context, path, NULL, NULL)) < 0) {
|
||||||
if (ret < 0) {
|
fprintf(stderr, "Failed to open input: %s\n", av_err2str(ret));
|
||||||
fprintf(stderr, "Failed to open input\n");
|
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
avformat_find_stream_info (data->fmt_context, NULL);
|
if ((ret = avformat_find_stream_info(data->encoded.format_context, NULL)) < 0) {
|
||||||
|
fprintf(stderr, "Could not find stream info: %s\n", av_err2str(ret));
|
||||||
data->ctx = avcodec_alloc_context3(NULL);
|
|
||||||
if (!data->ctx) {
|
|
||||||
fprintf(stderr, "Could not allocate audio codec context\n");
|
|
||||||
avformat_close_input(&data->fmt_context);
|
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We expect only one stream with audio
|
data->encoded.audio_stream = NULL;
|
||||||
data->astream = data->fmt_context->streams[0];
|
for (stream_index = 0; stream_index < data->encoded.format_context->nb_streams; ++stream_index) {
|
||||||
avcodec_parameters_to_context (data->ctx, data->astream->codecpar);
|
AVStream *stream = data->encoded.format_context->streams[stream_index];
|
||||||
|
codecpar = stream->codecpar;
|
||||||
if (data->ctx->codec_type != AVMEDIA_TYPE_AUDIO) {
|
if (codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
|
||||||
fprintf(stderr, "Not an audio file\n");
|
if (data->verbose) {
|
||||||
avformat_close_input(&data->fmt_context);
|
fprintf(stderr, "Stream #%u in media is an audio stream with codec \"%s\"\n",
|
||||||
|
stream_index, avcodec_get_name(codecpar->codec_id));
|
||||||
|
}
|
||||||
|
data->encoded.audio_stream = stream;
|
||||||
|
data->encoded.stream_index = stream_index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data->encoded.audio_stream == NULL) {
|
||||||
|
fprintf(stderr, "Could not find audio stream in media\n");
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("Number of streams: %d Codec id: %x\n", data->fmt_context->nb_streams,
|
data->encoded.packet = av_packet_alloc();
|
||||||
data->ctx->codec_id);
|
|
||||||
|
|
||||||
/* FFmpeg 5.1 (which contains libavcodec 59.37.100) introduced
|
/* FFmpeg 5.1 (which contains libavcodec 59.37.100) introduced
|
||||||
* a new channel layout API and deprecated the old one. */
|
* a new channel layout API and deprecated the old one. */
|
||||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 37, 100)
|
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 37, 100)
|
||||||
num_channels = data->ctx->ch_layout.nb_channels;
|
num_channels = codecpar->ch_layout.nb_channels;
|
||||||
#else
|
#else
|
||||||
num_channels = data->ctx->channels;
|
num_channels = codecpar->channels;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
data->rate = data->ctx->sample_rate;
|
data->rate = codecpar->sample_rate;
|
||||||
data->channels = num_channels;
|
data->channels = num_channels;
|
||||||
data->sfmt = data->ctx->sample_fmt;
|
/* Stride is not relevant for encoded audio. Set it to 1 to make sure
|
||||||
data->stride = 1; // Don't care
|
* the code in on_process() performs correct calculations. */
|
||||||
|
data->stride = 1;
|
||||||
|
|
||||||
bits_per_sample = av_get_bits_per_sample(data->ctx->codec_id);
|
bits_per_sample = av_get_bits_per_sample(codecpar->codec_id);
|
||||||
data->bitrate = bits_per_sample ?
|
data->bitrate = bits_per_sample ?
|
||||||
data->ctx->sample_rate * num_channels * bits_per_sample : data->ctx->bit_rate;
|
data->rate * num_channels * bits_per_sample : codecpar->bit_rate;
|
||||||
|
|
||||||
data->spa_format = SPA_AUDIO_FORMAT_ENCODED;
|
data->spa_format = SPA_AUDIO_FORMAT_ENCODED;
|
||||||
data->fill = playback_fill_fn(data->spa_format);
|
data->fill = encoded_playback_fill;
|
||||||
|
|
||||||
if (data->verbose)
|
if (data->verbose) {
|
||||||
printf("Opened file \"%s\" sample format %08x channels:%d rate:%d bitrate: %d\n",
|
printf("Opened file \"%s\" with encoded audio; channels:%d rate:%d bitrate: %d time units %d/%d\n",
|
||||||
data->filename, data->ctx->sample_fmt, data->channels,
|
data->filename, data->channels, data->rate, data->bitrate,
|
||||||
data->rate, data->bitrate);
|
data->encoded.audio_stream->time_base.num, data->encoded.audio_stream->time_base.den);
|
||||||
|
|
||||||
if (data->fill == NULL) {
|
|
||||||
fprintf(stderr, "Unhandled encoded format %d\n", data->spa_format);
|
|
||||||
avformat_close_input(&data->fmt_context);
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
avformat_close_input(&data->fmt_context);
|
|
||||||
|
|
||||||
data->encoded_file = fopen(data->filename, "rb");
|
|
||||||
if (!data->encoded_file) {
|
|
||||||
fprintf(stderr, "Failed to open file\n");
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -1525,6 +1525,11 @@ int main(int argc, char *argv[])
|
||||||
} else if (spa_streq(prog, "pw-dsdplay")) {
|
} else if (spa_streq(prog, "pw-dsdplay")) {
|
||||||
data.mode = mode_playback;
|
data.mode = mode_playback;
|
||||||
data.data_type = TYPE_DSD;
|
data.data_type = TYPE_DSD;
|
||||||
|
#ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION
|
||||||
|
} else if (spa_streq(prog, "pw-encplay")) {
|
||||||
|
data.mode = mode_playback;
|
||||||
|
data.data_type = TYPE_ENCODED;
|
||||||
|
#endif
|
||||||
} else
|
} else
|
||||||
data.mode = mode_none;
|
data.mode = mode_none;
|
||||||
|
|
||||||
|
|
@ -1790,13 +1795,9 @@ int main(int argc, char *argv[])
|
||||||
spa_zero(info);
|
spa_zero(info);
|
||||||
info.media_type = SPA_MEDIA_TYPE_audio;
|
info.media_type = SPA_MEDIA_TYPE_audio;
|
||||||
|
|
||||||
ret = avcodec_ctx_to_info(&data, data.ctx, &info);
|
ret = av_codec_params_to_audio_info(&data, data.encoded.audio_stream->codecpar, &info);
|
||||||
if (ret < 0) {
|
if (ret < 0)
|
||||||
if (data.encoded_file) {
|
|
||||||
fclose(data.encoded_file);
|
|
||||||
}
|
|
||||||
goto error_bad_file;
|
goto error_bad_file;
|
||||||
}
|
|
||||||
params[0] = spa_format_audio_build(&b, SPA_PARAM_EnumFormat, &info);
|
params[0] = spa_format_audio_build(&b, SPA_PARAM_EnumFormat, &info);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -1890,11 +1891,6 @@ int main(int argc, char *argv[])
|
||||||
/* and wait while we let things run */
|
/* and wait while we let things run */
|
||||||
pw_main_loop_run(data.loop);
|
pw_main_loop_run(data.loop);
|
||||||
|
|
||||||
#ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION
|
|
||||||
if (data.encoded_file)
|
|
||||||
fclose(data.encoded_file);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* we're returning OK only if got to the point to drain */
|
/* we're returning OK only if got to the point to drain */
|
||||||
if (data.drained)
|
if (data.drained)
|
||||||
exit_code = EXIT_SUCCESS;
|
exit_code = EXIT_SUCCESS;
|
||||||
|
|
@ -1919,6 +1915,12 @@ error_no_main_loop:
|
||||||
sf_close(data.file);
|
sf_close(data.file);
|
||||||
if (data.midi.file)
|
if (data.midi.file)
|
||||||
midi_file_close(data.midi.file);
|
midi_file_close(data.midi.file);
|
||||||
|
#ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION
|
||||||
|
if (data.encoded.packet)
|
||||||
|
av_packet_free(&data.encoded.packet);
|
||||||
|
if (data.encoded.format_context)
|
||||||
|
avformat_close_input(&data.encoded.format_context);
|
||||||
|
#endif
|
||||||
pw_deinit();
|
pw_deinit();
|
||||||
return exit_code;
|
return exit_code;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue