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)
 | 
				
			||||||
	return (int)size;
 | 
								break;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int avcodec_ctx_to_info(struct data *data, AVCodecContext *ctx, struct spa_audio_info *info)
 | 
						memcpy(dest, packet->data, packet->size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return packet->size;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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:
 | 
				
			||||||
| 
						 | 
					@ -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