
// Genode back-end for:
// BMediaTrack


// libc.lib.so
#include <libc/component.h>  // with_libc


// Adapted (slightly) from original cnuke@genode -----------

// avcodec.so avformat.so avutil.so avresample.so
extern "C" {
#	include <libavcodec/avcodec.h>
#	include <libavformat/avformat.h>
#	include <libavutil/dict.h>
#	include <libavutil/frame.h>
#	include <libavutil/opt.h>
#	include <libavresample/avresample.h>
};  // ~extern "C"



class hog::FramesProducer
{
	// Decodes mp3 ..etc audio files, extracting/producing audio frames
	// to be fed to FramesConsumer.
	
public:  // Xtors

		FramesProducer( Genode::String<MAXPATHLEN> trackpath );
		~FramesProducer();
		
		void PrintToStream( const char * path ) const;
		uint64_t Duration() const;
		uint64_t count_frames() const;
		uint64_t current_frame() const;
		
		int ProduceBuffer( const size_t fill_threshold );
		
		hog::Frame_data & DecodedFrames();


public:  // Types

		struct Not_initialized { };


private:  // Code

	static void init();
		void _close();


private:  // Data

		hog::Frame_data  decodedFrames;
		
#define USE_AVR 1
#if USE_AVR
		AVFrame         *_conv_frame = nullptr;  // decoded & resampled audio
#endif
		
		AVFormatContext *_format_ctx = nullptr;  // container (?)
		AVStream        *_stream     = nullptr;  // a "track" in Be parlance
		AVCodecContext  *_codec_ctx  = nullptr;  // codec
		AVPacket         _packet;  // encoded audio
		// contrary to the libav terminology, those are Frame*Buffers* (containing 1000+ frames), not single frames:
		AVFrame         *_frame      = nullptr;  // decoded audio
		
#if USE_AVR
		AVAudioResampleContext *_avr = nullptr;
		
		///later: get rid of this, now that progress() is gone (moved to class Consumer) ?
		unsigned samplesDecoded = 0;  // count of overall samples, for all channels -- i.e. 2*frames_decoded
#endif
};



hog::FramesProducer::FramesProducer( Genode::String<MAXPATHLEN> filepath )
{
	// Note:
	// MAXPATHLEN == 1024 on Genode.
	
	bool initialized = false;

	Libc::with_libc([&] ()
	{
		FramesProducer::init();

		_frame = av_frame_alloc();
		if (!_frame)
			return;
		
#if USE_AVR
		_conv_frame = av_frame_alloc();
		if (!_conv_frame) {
			av_free(_frame);
			return;
		}
#endif

		int err = 0;
		err = avformat_open_input(&_format_ctx, filepath.string(), NULL, NULL);
		if (err != 0) {
			Genode::error("could not open file: ", filepath);
#if USE_AVR
			av_free(_conv_frame);
#endif
			av_free(_frame);
			return;
		}

		err = avformat_find_stream_info(_format_ctx, NULL);
		if (err < 0) {
			Genode::warning("could not find the stream/track info (corrupt audio file ?)");
			_close();
			return;
		}

		for (unsigned i = 0; i < _format_ctx->nb_streams; ++i)
		{
			// pick the first _audio_ track/stream
			if (_format_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
				_stream = _format_ctx->streams[i];
				break;
			}
		}

		if (_stream == nullptr) {
			Genode::error("could not find any audio stream/track");
			_close();
			return;
		}

		_codec_ctx        = _stream->codec;
		_codec_ctx->codec = avcodec_find_decoder(_codec_ctx->codec_id);
		if (_codec_ctx->codec == NULL) {
			Genode::error("could not find decoder");
			_close();
			return;
		}

		err = avcodec_open2(_codec_ctx, _codec_ctx->codec, NULL);
		if (err != 0) {
			Genode::error("could not open decoder");
			_close();
			return;
		}

#if USE_AVR
		_avr = avresample_alloc_context();
		if (!_avr) {
			Genode::error("could not alloc ReSampler");
			_close();
			return;
		}
#endif  // ~USE_AVR
		
#if USE_AVR
		// Real-world example:
		// e.g. beos.mp3:
		//	44100, 2 chan, s16p, 128 Kb/s  (s16p means "samples: 16-bit planar", which we convert to interleaved)
		const uint64_t in_ch_layout =
			av_get_default_channel_layout( _codec_ctx->channels );  // fixed via cnuke -- thanks for the continued support with this code!
		av_opt_set_int(_avr, "in_channel_layout",  in_ch_layout, 0 );  // do not hardcode AV_CH_LAYOUT_STEREO, use the actual input layout type, which might be "MONO"
		av_opt_set_int(_avr, "in_sample_rate",     _codec_ctx->sample_rate, 0);
		av_opt_set_int(_avr, "in_sample_fmt",      _codec_ctx->sample_fmt,  0);
		av_opt_set_int(_avr, "out_channel_layout", AV_CH_LAYOUT_STEREO,     0);  // we always resample/convert to 'stereo'
#	if 0
		// disable resampling, by setting an 'out' rate identical to the 'in' rate:
		int in_smprate = _codec_ctx->sample_rate;
		Genode::log( "IN SMP RATE: ", in_smprate );
		// could also try to UPsample (to 48000) in case that bug is related to downsampling ?
		av_opt_set_int(_avr, "out_sample_rate",    in_smprate,  0);
#	else
		av_opt_set_int(_avr, "out_sample_rate",    Audio_out::SAMPLE_RATE,  0);
#	endif
#	if 1
		// Bare Genode session needs float:
		av_opt_set_int(_avr, "out_sample_fmt",     AV_SAMPLE_FMT_FLT,       0);
#	else
		// OSS needs int16:
		av_opt_set_int(_avr, "out_sample_fmt",     AV_SAMPLE_FMT_S16,       0);
		/// -- nope, deal with float still (or Optim that, don't double-convert, maybe ?)
#	endif
		//Genode::log("codec channels: ", _codec_ctx->channels);
		//Genode::log("resamp: setting in_sample_rate = ", _codec_ctx->sample_rate);
		//Genode::log("resamp: setting in_sample_fmt = ", av_get_sample_fmt_name(_codec_ctx->sample_fmt));

		if (avresample_open(_avr) < 0) {
			Genode::error("could not Open resampler...");
			_close();
			return;
		}

		_conv_frame->channel_layout = AV_CH_LAYOUT_STEREO;
		_conv_frame->sample_rate    = Audio_out::SAMPLE_RATE;
#	if 1
		_conv_frame->format         = AV_SAMPLE_FMT_FLT;
#	else
		_conv_frame->format         = AV_SAMPLE_FMT_S16;
#	endif
		
		// The above "out" layout means the resampler will produce interleaved floats
		// that can easily be pushed (and popped) in chunks of AUDIO_OUT_PACKET_SIZE bytes.
#endif  // ~USE_AVR
		
		av_init_packet( &_packet );  //xxx in AV sample code "transcode_aac.c", the packet is init + free'd at each frame.. ?
			// talking of AV sample code, libav/tests/audiogen.c seems interesting (creates an s16 wave file) --- and avresample-test.c even more so
		
		// --- Metadata (ID3 tags etc) ---
		//
		//bool const is_vorbis = _codec_ctx->codec_id == AV_CODEC_ID_VORBIS;
		//... metadata code here ...
		
		initialized = true;
	}); /* with_libc */
	
	// caught in BMediaFile ctor:
	if (!initialized) {
		throw Not_initialized();
	}
	
	GeTRACE("producer: init done");
}


hog::FramesProducer::~FramesProducer()
{
	Libc::with_libc([&] () {
#if USE_AVR
		avresample_close(_avr);
		avresample_free(&_avr);
#endif
		avcodec_close(_codec_ctx);
		
		avformat_close_input(&_format_ctx);
#if USE_AVR
		av_free(_conv_frame);
#endif
		av_frame_free(&_frame);  // [patched-Genode]: use av_frame_free() instead of av_free(_frame) like genode-world does, as the upstream libav sample code uses av_frame_free() and the headers recommend doing so too; the original variant does not seem to leak though.
	}); /* with_libc */
}


void hog::FramesProducer::_close()
{
	Libc::with_libc([&] () {
		avformat_close_input(&_format_ctx);
#if USE_AVR
		av_free(_conv_frame);
#endif
		av_frame_free(&_frame);  // [patched-Genode]: see above
	}); /* with_libc */
}


// Initialize libav once before it gets used
//
void hog::FramesProducer::init()
{
	static bool registered = false;
	if (registered)
		return;
	
	Genode::log( "== Initializing libAV ==" );
	
	Libc::with_libc([&] () {
		// setup decoders ..etc
		av_register_all();
		
		// This disables stderr output like "[mp3 @ 0xb4fb60] max_analyze_duration 5000000 reached"
		// and the (more annoying) ongoing ones (on e.g. beos.mp3) "underflow/overflow while reading.."
		// Warning: this disables not only those messages but also av_dump_format() output !
		av_log_set_level(AV_LOG_QUIET);
		
		// Hence I'm tempted to re-enable it ?
		//xxx Maybe it's important to be Quiet, to avoid printf-in-genode-context crashes ?
	}); /* with_libc */

	registered = true;
}

void hog::FramesProducer::PrintToStream( const char * path ) const
{
	// dump to stderr (only if AV_LOG_QUIET is unset)
	Libc::with_libc([&] () {
		av_dump_format(_format_ctx, 0, path, 0);
	}); /* with_libc */
	
	/*
	if( _codec_ctx )
	{
		Genode::log( "* in_channel_layout: ",	av_get_default_channel_layout(_codec_ctx->channels) );
		Genode::log( "* in_sample_rate: ",	_codec_ctx->sample_rate );
		Genode::log( "* in_sample_fmt: ",
			Genode::Hex(_codec_ctx->sample_fmt), " aka: ", av_get_sample_fmt_name(_codec_ctx->sample_fmt),
			" (", av_get_bytes_per_sample(_codec_ctx->sample_fmt), " bytes per sample, 2x per stereo frame)",
			// av_sample_fmt_is_planar(..)
			" (vs FLT-Planar: ", Genode::Hex(AV_SAMPLE_FMT_FLTP), " or S16-Planar: ", Genode::Hex(AV_SAMPLE_FMT_S16P), ")"
			);
	}
	*/
}

uint64_t hog::FramesProducer::Duration() const
{
	///assert( _format_ctx )..
	return _format_ctx->duration;
}



uint64_t hog::FramesProducer::count_frames() const
{
	///ToDo: test this, in e.g. ArmyKnife/audio-preview
	/// ** as of 24.04 we're not always using AVR (depending on build flags), so the below should probably be ifdef'ed as well
	return
		_format_ctx->duration
		*
		Audio_out::SAMPLE_RATE  // [patched-Genode]: same as progress() above, we must do accounting in downstream (user-facing) sampling rate, not in internal sampling rate
		;
}

uint64_t hog::FramesProducer::current_frame() const
{
	///assert !null..
	return _codec_ctx->frame_number;
}



hog::Frame_data &
	hog::FramesProducer::DecodedFrames()
{
	return decodedFrames;
}




#if  ! USE_AVR
#	include "_ge_conv_samplefmt.cpp"  // class OnlyFmtConv
#endif  // ~USE_AVR



// param <fill_threshold>: stop decoding bytes ASAP after going past that 'max' threshold
int hog::FramesProducer::ProduceBuffer( const size_t fill_threshold )
{
	//GeTRACE("Producebuffer");
	
	size_t written = 0;

	Libc::with_libc([&]
	{
		// we don't want an open-ended loop (we *did* go into an infinite loop a few times), but we also want to
		// skip over irrelevant hunks, i.e. avoid giving up on the file if we stumble on a non-audio hunk (ID3 etc), hence this middle-of-the-road approach:
		const int ending_iteration = 10;
		
		for(
			int i = 0;
			i < ending_iteration;
			av_free_packet( &_packet ), i++  // !
			)
		{
			//GeTRACE(" decode");
			
			// demux a few hundred encoded frames into <_packet>
#if 0
			read raw WAV samples directly (test with legacy mixer first!) ?
#else
			int res = av_read_frame( _format_ctx, &_packet );
			if( AVERROR_EOF == res )
			{
				// libav's sample code recommends sending an extra 'empty' packet after the last partial one, to "flush" the decoder (see transcode_aac.c)
				Genode::log( " reached AVERROR_EOF, sending empty packet to flush decoder" );
				_packet.data = NULL;  // make extra sure the 'EOF' packet is empty
				_packet.size = 0;
				// proceed with decoding down there, but make sure it's the last run:
				
				i = ending_iteration;  // !
			}
			else if( res != 0 )
			{
				Genode::error( "Could not read frameset-chunk: ", res );
				
				i = ending_iteration;  // !
			}
			
			if( _packet.stream_index != _stream->index )
			{
				// Demuxer handed us a chunk from a stream distinct from the (audio) stream we selected: this occurs on some mp3s, and many ogg/vorbis files
				// (sometimes the demuxer does *not* give us that stream-index indication, and we end up in the "could not decode frame" case below, but that seems harmless)
				Genode::log( " unexpected stream packet idx ", _packet.stream_index, " -- probably an ID3 (album art etc) hunk ?" );
				
				continue;  // !
			}
			
			int finished = 0; //++ "got_full_frame" or "frame_count"
			
			// decode from <_packet> to <_frame>
			res = avcodec_decode_audio4( _codec_ctx, _frame, &finished, &_packet );
	//GeTRACE( " (CONSUMED ", res, " bytes) (_packet.size : ", _packet.size );
	// -> ?? : ""CONSUMED 1 bytes) (_packet.size : 1""
			if( res < 0 || finished <= 0 )
			{
				// Demuxing an ogg/vorbis file often yields 'undecodable' stream-chunks, which have the same ID as the main audio stream ID (!)
				// and thus do not get weeded out by the above if(..stream_index..), meaning we end up here, harmlessly(?) with:
				// "Could not decode frame -- err: 129  frame-count: 0"
				// Alternatively, this might occur when receiving the empty "flushing" packet:
				
				if( NULL == _packet.data && 0 == _packet.size )
					Genode::log( " nothing to decode (empty chunk/packet)" );
				else
					Genode::warning( "Could not decode frame, err: ", res, " ; frame-count: ", finished, " -- skipping ahead to next chunk" );
				// we get that on EOF it seems -- "err: -1052488119" -- maybe the encoder needs to be "flushed" ?
				
				continue;  // !  Do NOT bail out, iterate the for() loop some more until we demux a packet that's part of the wanted (audio) stream, otherwise playback would abort completely.
///ToDo: ogg/vorbis works again now with that "continue" statement making the decoder more robust; but no matter how robust the underlying decoder is, I should have "defense in depth": BMediaFile should not be "dead in the water" -- I tried to connect HasData() to fTearDown, but that does not fix it ? Let's re-create the bug (add a "return 0") to re-create the dead-in-water behavior, so that I can fix handling of it with HasData() et alia
			}
#endif
			
#if USE_AVR
			// resample from <_frame> to <_conv_frame>
				// From Genode's "audio_player":   ///ToDo: let's do some fine-grained monitoring of pd().used_ram().value to look for "small" memory leaks with ogg/vorbis et al; if absolutely no leak detected any more, remove this comment block
				 /* We have to read all available [resampled?] samples, otherwise we
				 * end up leaking memory. Draining all available sample will
				 * lead to distorted audio; checking for > 64 works(tm) but
				 * we might still leak some memory (hopefully the song's duration
				 * is not too long.) FWIW, it seems to be happening only when
				 * resampling vorbis files with FLTP format.	 */
			AVFrame * in = _frame;
			do {
				int res = avresample_convert_frame( _avr, _conv_frame, in );  // returns 0 on success
				if( res < 0 )
				{
					Genode::error("could not resample frames, aborting playback of this file");
					//
					av_free_packet( &_packet );  ///or could do "i = ending_iteration ; continue ;"

					return;  // !
				}
				
				GeTRACE( "   converted ", _conv_frame->nb_samples, " frames" );
				
				//Genode::log( "     avresample_convert_frame(", _conv_frame, ", ", in, ") -> ", res, "  [avresample_available: ", avresample_available(_avr), "]" );
				// on e.g. a 22050 Hz file, avresample_available() starts counting up in 16 bytes increments, despite playback being correct.. bug in libav?
				
				/*
				from https://libav.org/documentation/doxygen/master/group__lavu__sampfmts.html :
					For *planar* sample formats, each audio channel is in a separate
					data plane, and linesize is the buffer size, in bytes, for a single
					plane. All data planes must be the same size.
					For *packed* sample formats, only the first data plane is used,
					and samples for each channel are interleaved. In this case,
					linesize is the buffer size, in bytes, for the 1 plane.
				*/
				const void * data  = _conv_frame->extended_data[0];
#	if 1
				size_t const bytes_per_frame = 2 /* stereo */ * sizeof( float );
#	else
				size_t const bytes_per_frame = 2 /* stereo */ * sizeof( short );
#	endif
				
				//size_t const  bytes = _conv_frame->linesize[0];
				size_t const bytes =
					bytes_per_frame
					* _conv_frame->nb_samples  // i.e. number of frames, since the libAV doc says "number of audio samples (per channel)"
					;
					// [patched-Genode] This seems more accurate (looking at the LOG) than _conv_frame->linesize[0] : we cannot assume the whole line/size is always filled in full, the LOG indicates otherwise
				if( _conv_frame->nb_samples <= 0 )
				{
					// [patched-Genode] sometimes we can get in an infinite loop as avresample_available()
					// keeps returning > 0 forever ; but we can detect that condition because <_conv_frame->nb_samples> is zero
	///ToDo: send this warning to rl.log() instead
					//..... Genode::warning( "resample: bytes == ", bytes );
					//
					break;
					//
					
					// This trace'ing exhibits the problem (also in e.g. audio_player) when playing 22050 Hz etc mp3s:
					// ! <nb_samples> is zero after a while !
					//	Genode::log( "   _conv_frame->linesize = ", bytes, "  t+ samplesDecoded = ", samplesDecoded, "  nb_samples: ", _conv_frame->nb_samples);
				}
				
				// T0 for <decodedFrames> and <samplesDecoded>
				//
				
				//_d_ samplesDecoded += bytes / frame_size;
				samplesDecoded +=
					_conv_frame->nb_samples;///xx uh if "nb_samples" is actually a nb_frames, we should *2 to get the nb_samples..?
				//Genode::log( "  <samplesDecoded> + ", _conv_frame->nb_samples, " --> ", samplesDecoded );
				
				// T0
				auto latest_write =
					decodedFrames.write( data, bytes );
				written += bytes;  // even if write() failed to write any/all bytes for lack room in the FIFO, we must still account for actual "decoding" progress
				
				if( latest_write != bytes )
				{
					Genode::warning( "cannot stuff all frames into fifo, excess lost, decoder iterating faster than consumer ? (", written, " != ", bytes, ")" );
					/// we continue the do..while() loop anyway... to avoid leaks in the resampler.. correct ?
				}
				GeTRACE( "   ring.write(", bytes, ") bytes -> accumulated ", written, "  [avr fifo: ", avresample_available(_avr), " bytes]" );
				
				in = nullptr;  // !
			} while( avresample_available(_avr) > 0 );  // was : " > 64"
#	else  // ! USE_AVR:
		static /****/
			OnlyFmtConv fmtconv;
			
			fmtconv.SetFrom(
				_codec_ctx->sample_fmt,
				_frame->extended_data[0],  // planar left
				_frame->extended_data[1],  // planar right
				_frame->nb_samples
				);
			auto latest_write = //
				decodedFrames.write( fmtconv.Buf(), fmtconv.BufBytes() );
			written += fmtconv.BufBytes();  // even if write() failed to write any/all bytes for lack room in the FIFO, we must still account for actual "decoding" progress, if not for "playback" progress
#endif
			
			// with e.g. ogg/vorbis the decoder would provide 81920 bytes, definitely wouldn't enforce 50% threshold... So do moderate it:
			if( written >= fill_threshold )
				i = ending_iteration;  // !
		}
	}); /* with_libc */
	
#if 0
	{
		// Gen SINE Wave:
		static int frame_num = 0;
		frame_num += 1;//xxx
		float *data = (float*)_conv_frame->extended_data[0];
		
		for (int j = 0; j < _conv_frame->nb_samples; j++)
		    data[j] = sin(2 * M_PI * (frame_num + j) / frame_size);
	}
#endif
	
	return written;
}


