diff VideoPlayer.h @ 2134:992d2e6f907d

preparation for libavcodec
author zipi
date Tue, 31 Dec 2013 14:52:14 +0000
parents 633f81bb3ae7
children d24ee391fd1f
line wrap: on
line diff
--- a/VideoPlayer.h	Tue Dec 31 16:26:08 2013 +0600
+++ b/VideoPlayer.h	Tue Dec 31 14:52:14 2013 +0000
@@ -106,6 +106,669 @@
 
 void  ShowIntroVideo_and_LoadingScreen();
 
+extern "C"
+{
+#include "lib/libavcodec/avcodec.h"
+#include "lib/libavformat/avformat.h"
+#include "lib/libavutil/avutil.h"
+#include "lib/libavutil/imgutils.h"
+#include "lib/libswscale/swscale.h"
+#include "lib/libswresample/swresample.h"
+#include "lib/libavutil/opt.h"
+	//#include "libavutil/samplefmt.h"
+}
+#pragma comment(lib, "avcodec.lib")
+#pragma comment(lib, "avformat.lib")
+#pragma comment(lib, "avutil.lib")
+#pragma comment(lib, "swscale.lib")
+#pragma comment(lib, "swresample.lib")
+
+#include "lib/OpenAL/al.h"
+#include "lib/OpenAL/alc.h"
+#pragma comment(lib, "OpenAL32.lib")
+
+
+
+template<int MAX_SAMPLES_BUFFERS>
+class OpenALSoundProviderGeneric
+{
+public:
+	inline OpenALSoundProviderGeneric()
+	{
+		this->device = nullptr;
+		this->context = nullptr;
+		this->samples_current_buffer = 0;
+
+		this->samples_source_id = -1;
+		for (int i = 0; i < MAX_SAMPLES_BUFFERS; ++i)
+			samples_buffer_id[i] = -1;
+	}
+
+	inline bool Initialize()
+	{
+		auto device_names = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER);
+		if (!device_names)
+			device_names = alcGetString(nullptr, ALC_DEVICE_SPECIFIER);
+		if (device_names)
+		{
+			for (auto device_name = device_names; device_name[0]; device_name += strlen(device_name))
+			{
+				continue;
+			}
+		}
+
+		device = alcOpenDevice(nullptr);
+		if (!device || CheckError())
+			return false;
+
+		context = alcCreateContext(device, nullptr);
+		if (!context || CheckError())
+			return Release(), false;
+
+		alcMakeContextCurrent(context);
+
+		bool eax2 = alIsExtensionPresent("EAX2.0");
+		bool eax3 = alIsExtensionPresent("EAX3.0");
+		bool eax4 = alIsExtensionPresent("EAX4.0");
+		bool eax5 = alIsExtensionPresent("EAX5.0");
+
+		auto vendor = alGetString(AL_VERSION);
+		auto version = alGetString(AL_VERSION);
+		auto extensions = alcGetString(device, ALC_EXTENSIONS);
+
+		alGenBuffers(MAX_SAMPLES_BUFFERS, samples_buffer_id);
+		if (CheckError())
+			return Release(), false;
+
+		alGenSources(1, &samples_source_id);
+		if (CheckError())
+			return Release(), false;
+
+		float sound_pos[] = { 0.0f, 0.0f, 0.0f };
+		alSourcefv(samples_source_id, AL_POSITION, sound_pos);
+		alSourcei(samples_source_id, AL_LOOPING, AL_FALSE);
+
+		return true;
+	}
+
+	void Release()
+	{
+		if (samples_source_id != -1)
+		{
+			alDeleteSources(1, &samples_source_id);
+			samples_source_id = -1;
+		}
+
+		for (int i = 0; i < MAX_SAMPLES_BUFFERS; ++i)
+		{
+			if (samples_buffer_id[i] != -1)
+				alDeleteBuffers(1, &samples_buffer_id[i]);
+			samples_buffer_id[i] = -1;
+		}
+
+		alcMakeContextCurrent(nullptr);
+		if (context)
+		{
+			alcDestroyContext(context);
+			context = nullptr;
+		}
+		if (device)
+		{
+			alcCloseDevice(device);
+			device = nullptr;
+		}
+	}
+
+
+
+
+
+
+
+
+	void PlaySample(int num_channels, int sample_rate, int num_samples, void *samples)
+	{
+		//char msg[256];sprintf(msg, "chan %u rate %5u num %5u ptr %p\n", num_channels, sample_rate, num_samples, samples);
+		//log(msg);
+		ALenum sound_format;
+		switch (num_channels)
+		{
+		case 1: sound_format = AL_FORMAT_MONO16;    break;
+		case 2: sound_format = AL_FORMAT_STEREO16;  break;
+		default:
+			if (bool multichannel = alIsExtensionPresent("AL_EXT_MCFORMATS"))
+			{
+				switch (num_channels)
+				{
+				case 4: sound_format = alGetEnumValue("AL_FORMAT_QUAD16");  break;
+				case 6: sound_format = alGetEnumValue("AL_FORMAT_51CHN16"); break;
+				case 7: sound_format = alGetEnumValue("AL_FORMAT_61CHN16"); break;
+				case 8: sound_format = alGetEnumValue("AL_FORMAT_71CHN16"); break;
+				}
+			}
+			__debugbreak();
+		}
+
+		float listener_pos[] = { 0.0f, 0.0f, 0.0f };
+		float listener_vel[] = { 0.0f, 0.0f, 0.0f };
+		float listener_orientation[] = { 0.0f, 0.0f, -1.0f, // direction
+			0.0f, 1.0f, 0.0f }; // up vector
+		alListenerfv(AL_POSITION, listener_pos);
+		alListenerfv(AL_VELOCITY, listener_vel);
+		alListenerfv(AL_ORIENTATION, listener_orientation);
+
+		unsigned int *next_buffer = samples_buffer_id + samples_current_buffer;
+
+		alBufferData(*next_buffer, sound_format, samples, num_samples * sizeof(__int16), sample_rate);
+		CheckError();
+
+
+		int num_processed_buffers;
+		alGetSourcei(samples_source_id, AL_BUFFERS_PROCESSED, &num_processed_buffers);
+		while (num_processed_buffers)
+		{
+			unsigned int processed_buffers_id[4];
+			alSourceUnqueueBuffers(samples_source_id, min(4, num_processed_buffers), processed_buffers_id);
+			CheckError();
+			alGetSourcei(samples_source_id, AL_BUFFERS_PROCESSED, &num_processed_buffers);
+		}
+
+		int num_queued_buffers;
+		alGetSourcei(samples_source_id, AL_BUFFERS_QUEUED, &num_queued_buffers);
+		if (num_queued_buffers >= MAX_SAMPLES_BUFFERS)
+		{
+			__debugbreak();
+		}
+
+		alSourceQueueBuffers(samples_source_id, 1, next_buffer);
+		CheckError();
+
+		int status;
+		alGetSourcei(samples_source_id, AL_SOURCE_STATE, &status);
+		if (status != AL_PLAYING)
+		{
+			alSourcePlay(samples_source_id);
+			CheckError();
+		}
+
+		if (++samples_current_buffer >= MAX_SAMPLES_BUFFERS)
+			samples_current_buffer = 0;
+	}
+
+
+
+protected:
+	ALCdevice    *device;
+	ALCcontext   *context;
+	unsigned int  samples_buffer_id[MAX_SAMPLES_BUFFERS];
+	unsigned int  samples_source_id;
+	int           samples_current_buffer;
+
+
+	bool CheckError()
+	{
+		ALenum code = alcGetError(device);
+		if (code != ALC_NO_ERROR)
+		{
+			DWORD w;
+			const char *message = alcGetString(device, code);
+			WriteConsoleA(GetStdHandle(STD_OUTPUT_HANDLE), message, lstrlenA(message), &w, nullptr);
+			return true;
+		}
+		return false;
+	}
+};
+typedef OpenALSoundProviderGeneric<16> OpenALSoundProvider;
+
+
+
+void av_logger(void *, int, const char *format, va_list args);
+
+
+
+#include <memory>
+using std::tr1::shared_ptr;
+using std::tr1::make_shared;
+
+class MultimediaFrame
+{
+public:
+	typedef shared_ptr<MultimediaFrame> Ptr;
+	inline MultimediaFrame(AVMediaType type, AVPacket *packet, AVCodecContext *dec_ctx)
+	{
+		this->type = type;
+		this->f = nullptr;
+		this->p = packet;
+		this->dec_ctx = dec_ctx;
+	}
+	virtual ~MultimediaFrame() { return; }
+
+
+	AVMediaType  Type() const { return type; }
+	AVFrame     *GetAVFrame() { return f; }
+
+	virtual int   Decode() = 0;
+	virtual void *GetData() = 0;
+	virtual int   GetDataPitch() = 0;
+
+
+protected:
+	AVMediaType      type;
+	AVFrame         *f;
+	AVPacket        *p;
+	AVCodecContext  *dec_ctx;
+};
+
+
+
+class MultimediaVideoFrame : public MultimediaFrame
+{
+public:
+	inline MultimediaVideoFrame(AVMediaType type, AVPacket *packet, AVCodecContext *dec_ctx, int width, int height) :
+		MultimediaFrame(type, packet, dec_ctx)
+	{
+		this->width = width;
+		this->height = height;
+		this->rescaled_data[0] = nullptr;
+		this->rescaled_linesize[0] = 0;
+	}
+	virtual ~MultimediaVideoFrame()
+	{
+		av_freep(&rescaled_data);
+		av_frame_free(&f);
+	}
+
+	int Decode() override
+	{
+		f = avcodec_alloc_frame();
+		avcodec_get_frame_defaults(f);
+
+		volatile int done = false;
+		do
+		{
+			int ret;
+			if ((ret = avcodec_decode_video2(dec_ctx, f, (int *)&done, p)) < 0)
+				return ret;
+		} while (!done);
+		if (Rescale(f, width, height, AV_PIX_FMT_RGB32, rescaled_data, rescaled_linesize))
+			return 0;
+		else return -1;
+	}
+
+	virtual void *GetData()       { return rescaled_data[0]; }
+	virtual int   GetDataPitch()  { return rescaled_linesize[0]; }
+
+protected:
+	int      width;
+	int      height;
+	uint8_t *rescaled_data[8];
+	int      rescaled_linesize[8];
+
+	bool Rescale(AVFrame *frame, int dst_width, int dst_height, AVPixelFormat format, uint8_t **out_data, int *out_linesize)
+	{
+		if (av_image_alloc(out_data, out_linesize, dst_width, dst_height, format, 1) < 0)
+			return false;
+
+		SwsContext *converter = sws_getContext(frame->width, frame->height, (AVPixelFormat)frame->format,
+			dst_width, dst_height, format,
+			SWS_BICUBIC, nullptr, nullptr, nullptr);
+		sws_scale(converter, frame->data, frame->linesize, 0, frame->height, out_data, out_linesize);
+		sws_freeContext(converter);
+
+		return true;
+	}
+};
+
+
+
+class MultimediaAudioFrame : public MultimediaFrame
+{
+public:
+	inline MultimediaAudioFrame(AVMediaType type, AVPacket *packet, AVCodecContext *dec_ctx) :
+		MultimediaFrame(type, packet, dec_ctx)
+	{
+		this->resampled_data = nullptr;
+	}
+	virtual ~MultimediaAudioFrame()
+	{
+		av_free(resampled_data);
+		av_frame_free(&f);
+	}
+
+	int Decode() override
+	{
+		f = avcodec_alloc_frame();
+		avcodec_get_frame_defaults(f);
+
+		volatile int done = false;
+		do
+		{
+			int ret;
+			if ((ret = avcodec_decode_audio4(dec_ctx, f, (int *)&done, p)) < 0)
+				return ret;
+		} while (!done);
+		if (Resample(f, f->channel_layout, f->sample_rate,
+			f->channel_layout, f->sample_rate, AV_SAMPLE_FMT_S16, &resampled_data))
+			return 0;
+		else return -1;
+	}
+
+	virtual void *GetData()       { return resampled_data; }
+	virtual int   GetDataPitch()  { return 2 * f->nb_samples; }
+
+protected:
+	uint8_t *resampled_data;
+
+	bool Resample(AVFrame *frame,
+		int64_t src_channel_layout, int src_sample_rate,
+		int64_t dst_channel_layout, int dst_sample_rate, AVSampleFormat dst_format, uint8_t **out_data)
+	{
+		SwrContext *converter = swr_alloc();
+
+		av_opt_set_int(converter, "in_channel_layout", src_channel_layout, 0);
+		av_opt_set_int(converter, "in_sample_rate", src_sample_rate, 0);
+		av_opt_set_sample_fmt(converter, "in_sample_fmt", (AVSampleFormat)frame->format, 0);
+
+		av_opt_set_int(converter, "out_channel_layout", dst_channel_layout, 0);
+		av_opt_set_int(converter, "out_sample_rate", dst_sample_rate, 0);
+		av_opt_set_sample_fmt(converter, "out_sample_fmt", dst_format, 0);
+
+		if (swr_init(converter) < 0)
+			return false;
+
+		int dst_nb_samples;
+		int max_dst_nb_samples = dst_nb_samples = av_rescale_rnd(frame->nb_samples, dst_sample_rate, src_channel_layout, AV_ROUND_UP);
+
+		uint8_t **dst_data;
+		int       dst_linesize;
+		int dst_nb_channels = av_get_channel_layout_nb_channels(dst_channel_layout);
+		if (av_samples_alloc_array_and_samples(&dst_data, &dst_linesize, dst_nb_channels, dst_nb_samples, dst_format, 0) < 0)
+		{
+			swr_free(&converter);
+			return false;
+		}
+
+		dst_nb_samples = av_rescale_rnd(swr_get_delay(converter, src_sample_rate) + frame->nb_samples, dst_sample_rate, src_sample_rate, AV_ROUND_UP);
+		if (dst_nb_samples > max_dst_nb_samples)
+		{
+			av_free(dst_data[0]);
+			if (av_samples_alloc(dst_data, &dst_linesize, dst_nb_channels, dst_nb_samples, dst_format, 1) < 0)
+			{
+				swr_free(&converter);
+				return false;
+			}
+			max_dst_nb_samples = dst_nb_samples;
+		}
+
+
+		if (swr_convert(converter, dst_data, dst_nb_samples, (const uint8_t **)frame->data, frame->nb_samples) < 0)
+		{
+			av_free(dst_data[0]);
+			swr_free(&converter);
+			return false;
+		}
+
+		*out_data = dst_data[0];
+		return true;
+	}
+};
+
+
+template<int NUM_PRECACHED_FRAMES>
+class MovieCached
+{
+public:
+	bool Stopped() const { return stopped; }
+
+protected:
+	friend class MultimediaPlayer;
+	inline MovieCached(OpenALSoundProvider *sound_provider)
+	{
+		this->format_ctx = nullptr;
+		this->sound_provider = sound_provider;
+		this->stopped = false;
+
+		this->video_stream_idx = -1;
+		this->video_stream = nullptr;
+		this->video_stream_dec = nullptr;
+		this->video_stream_dec_ctx = nullptr;
+
+		this->audio_stream_idx = -1;
+		this->audio_stream = nullptr;
+		this->audio_stream_dec = nullptr;
+		this->audio_stream_dec_ctx = nullptr;
+	}
+
+	bool Load(const char *video_filename, int width, int height)
+	{
+		this->width = width;
+		this->height = height;
+
+		if (avformat_open_input(&format_ctx, video_filename, nullptr, nullptr) >= 0)
+		{
+			if (avformat_find_stream_info(format_ctx, nullptr) >= 0)
+			{
+				av_dump_format(format_ctx, 0, video_filename, 0);
+
+				video_stream_idx = OpenStream(AVMEDIA_TYPE_VIDEO, &video_stream, &video_stream_dec, &video_stream_dec_ctx);
+				if (video_stream_idx < 0)
+					return Release(), false;
+
+				audio_stream_idx = OpenStream(AVMEDIA_TYPE_AUDIO, &audio_stream, &audio_stream_dec, &audio_stream_dec_ctx);
+				if (audio_stream_idx < 0)
+					return Release(), false;
+
+				strcpy(movie_name, video_filename);
+				packet = new AVPacket;
+				av_init_packet(packet);
+				return true;
+			}
+		}
+		return Release(), false;
+	}
+
+	bool Release()
+	{
+		if (format_ctx)
+		{
+			avformat_free_context(format_ctx);
+			format_ctx = nullptr;
+		}
+		if (packet)
+		{
+			av_free_packet(packet);
+			delete packet;
+			packet = nullptr;
+		}
+
+		if (video_stream_idx >= 0)
+		{
+			video_stream_idx = -1;
+			video_stream = nullptr;
+			video_stream_dec = nullptr;
+			avcodec_close(video_stream_dec_ctx);
+			video_stream_dec_ctx = nullptr;
+		}
+
+		if (audio_stream_idx >= 0)
+		{
+			audio_stream_idx = -1;
+			audio_stream = nullptr;
+			audio_stream_dec = nullptr;
+			avcodec_close(audio_stream_dec_ctx);
+		}
+		return true;
+	}
+
+
+	int OpenStream(AVMediaType type, AVStream **out_stream, AVCodec **out_dec, AVCodecContext **out_dec_ctx)
+	{
+		int stream_idx = av_find_best_stream(format_ctx, type, -1, -1, nullptr, 0);
+		if (stream_idx < 0)
+			return stream_idx;
+
+		auto stream = format_ctx->streams[stream_idx];
+		auto dec_ctx = stream->codec;
+		auto dec = avcodec_find_decoder(dec_ctx->codec_id);
+		if (dec)
+		{
+			if (avcodec_open2(dec_ctx, dec, nullptr) >= 0)
+			{
+				*out_stream = stream;
+				*out_dec = dec;
+				*out_dec_ctx = dec_ctx;
+				return stream_idx;
+			}
+		}
+		return -1;
+	}
+
+
+	MultimediaFrame::Ptr GetNextFrame()
+	{
+		packet->data = nullptr;
+		packet->size = 0;
+
+		volatile int got_frame = false;
+		do
+		{
+			if (av_read_frame(format_ctx, packet) < 0)
+			{
+				stopped = true;
+				return nullptr;
+			}
+		} while (packet->stream_index != video_stream_idx &&
+			packet->stream_index != audio_stream_idx);
+
+		if (packet->stream_index == video_stream_idx)
+			return MultimediaFrame::Ptr(new MultimediaVideoFrame(AVMEDIA_TYPE_VIDEO, packet, video_stream_dec_ctx, width, height));
+		else if (packet->stream_index == audio_stream_idx)
+			return MultimediaFrame::Ptr(new MultimediaAudioFrame(AVMEDIA_TYPE_AUDIO, packet, audio_stream_dec_ctx));
+		return nullptr;
+	}
+
+
+
+	char                 movie_name[256];
+	int                  width;
+	int                  height;
+	bool                 stopped;
+	AVFormatContext     *format_ctx;
+	//AVFrame             *frame;
+	AVPacket            *packet;
+	//MultimediaFrame     *frames[NUM_PRECACHED_FRAMES];
+	OpenALSoundProvider *sound_provider;
+
+	int              video_stream_idx;
+	AVStream        *video_stream;
+	AVCodec         *video_stream_dec;
+	AVCodecContext  *video_stream_dec_ctx;
+
+	int              audio_stream_idx;
+	AVStream        *audio_stream;
+	AVCodec         *audio_stream_dec;
+	AVCodecContext  *audio_stream_dec_ctx;
+};
+typedef MovieCached<10> Movie;
+
+
+
+
+class MultimediaPlayer
+{
+public:
+	inline MultimediaPlayer()
+	{
+	}
+
+	bool Initialize()
+	{
+		if (!libavcodec_initialized)
+		{
+			av_log_set_callback(Logger);
+			avcodec_register_all();
+			av_register_all();
+
+			libavcodec_initialized = true;
+		}
+
+		sound_provider = new OpenALSoundProvider;
+		sound_provider->Initialize();
+
+		return true;
+	}
+
+
+	Movie *LoadMovie(const char *filename, int width, int height)
+	{
+		auto movie = new Movie(sound_provider);
+		if (movie)
+		{
+			if (movie->Load(filename, width, height))
+			{
+				current_movie_width = width;
+				current_movie_height = height;
+				return current_movie = movie;
+			}
+			delete movie;
+		}
+		return nullptr;
+	}
+
+
+	inline char *DoFrame()
+	{
+		if (!current_movie)
+			return nullptr;
+
+		while (true)
+		{
+			auto frame = current_movie->GetNextFrame();
+			if (!frame)
+				return nullptr;
+
+			if (frame->Type() == AVMEDIA_TYPE_AUDIO)
+			{
+				uint8_t *data;
+				if (frame->Decode() >= 0)
+				{
+					auto f = frame->GetAVFrame();
+					sound_provider->PlaySample(f->channels, f->sample_rate, f->nb_samples, frame->GetData());
+					Sleep(20);
+					continue;
+				}
+			}
+			else if (frame->Type() == AVMEDIA_TYPE_VIDEO)
+			{
+				uint8_t *dst_data[4] = { 0 };
+				int      dst_linesize[4] = { 0 };
+				if (frame->Decode() >= 0)
+				{
+					auto image = new char[current_movie_width * current_movie_height * 4];
+					memcpy(image, frame->GetData(), current_movie_height * frame->GetDataPitch());
+
+					return image;
+				}
+			}
+			return nullptr;
+		}
+	}
+
+
+
+protected:
+	static void Logger(void *, int, const char *format, va_list args);
+
+	OpenALSoundProvider *sound_provider;
+	Movie               *current_movie;
+	int                  current_movie_width;
+	int                  current_movie_height;
+
+	static bool libavcodec_initialized;
+};
+
+
+