Mercurial > mm7
diff MediaPlayer.cpp @ 2315:58be29479e75
add files
author | Ritor1 |
---|---|
date | Wed, 19 Mar 2014 10:09:42 +0600 |
parents | |
children | 9987f93d7e1f |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MediaPlayer.cpp Wed Mar 19 10:09:42 2014 +0600 @@ -0,0 +1,820 @@ +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 <vector> +#include <deque> + +#include "stuff.h" +#include "OpenALSoundProvider.h" + +#include "MediaPlayer.h" +using namespace Media; + + + + +class MemoryStream +{ + public: + inline MemoryStream(void *data, size_t data_size) + { + this->data_size = data_size; + this->data = data; + this->current_pos = 0; + } + inline MemoryStream() + { + this->data_size = 0; + this->data = nullptr; + this->current_pos = 0; + } + + inline ~MemoryStream() + { + if (data) + delete [] data; + } + + inline size_t Write(void *buffer, size_t num_bytes) + { + if (!data) + { + data_size = 32 + num_bytes; + data = new char[data_size]; + current_pos = 0; + } + else if (current_pos + num_bytes >= data_size) + { + int new_data_size = data_size + num_bytes + data_size / 8 + 4; + auto new_data = new char[new_data_size]; + + memcpy(new_data, data, data_size); + delete [] data; + + data_size = new_data_size; + data = new_data; + } + memcpy((char *)data + current_pos, buffer, num_bytes); + current_pos += num_bytes; + return num_bytes; + } + + inline size_t Read(void *buffer, size_t num_bytes) + { + size_t read_size = min(num_bytes, data_size - current_pos); + if (read_size) + { + memcpy(buffer, (char *)data + current_pos, read_size); + current_pos += read_size; + } + return read_size; + } + + inline void Reset() + { + current_pos = 0; + } + inline void SeekToEnd() + { + current_pos = data_size; + } + + inline size_t Unwind(size_t bytes) + { + if (bytes > current_pos) + current_pos = 0; + else + current_pos -= bytes; + return current_pos; + } + + inline size_t Rewind(size_t bytes) + { + if (current_pos + bytes >= data_size) + current_pos = data_size; + else + current_pos += bytes; + return current_pos; + } + + inline size_t Size() const {return data_size;} + inline size_t Current() const {return current_pos;} + inline void *Ptr() const {return data;} + + private: + void *data; + size_t data_size; + size_t current_pos; +}; + + + + +OpenALSoundProvider *provider = nullptr; + + + +static int av_num_bytes_per_sample(AVSampleFormat sample_fmt) +{ + switch (sample_fmt) + { + case AV_SAMPLE_FMT_U8: + case AV_SAMPLE_FMT_U8P: + return 1; + + case AV_SAMPLE_FMT_S16: + case AV_SAMPLE_FMT_S16P: + return 2; + + case AV_SAMPLE_FMT_S32: + case AV_SAMPLE_FMT_S32P: + case AV_SAMPLE_FMT_FLT: + case AV_SAMPLE_FMT_FLTP: + return 4; + + case AV_SAMPLE_FMT_DBL: + case AV_SAMPLE_FMT_DBLP: + return 8; + + default: + case AV_SAMPLE_FMT_NONE: + Error("Invalid av sample format: %u", sample_fmt); + } + return 0; +} + + + +struct AVStreamWrapper +{ + inline AVStreamWrapper() + { + this->type = AVMEDIA_TYPE_UNKNOWN; + this->stream_idx = -1; + this->stream = nullptr; + this->dec = nullptr; + this->dec_ctx = nullptr; + } + + inline void Release() + { + type = AVMEDIA_TYPE_UNKNOWN; + stream_idx = -1; + stream = nullptr; + dec = nullptr; + if (dec_ctx) + { + avcodec_close(dec_ctx); + dec_ctx = nullptr; + } + } + + AVMediaType type; + int stream_idx; + AVStream *stream; + AVCodec *dec; + AVCodecContext *dec_ctx; +}; + +struct AVAudioStream: public AVStreamWrapper +{ + inline AVAudioStream(): + AVStreamWrapper() + { + this->bytes_per_sample = 0; + this->bytes_per_second = 0; + } + + int bytes_per_sample; + int bytes_per_second; +}; + +struct AVVideoStream: public AVStreamWrapper +{ + inline AVVideoStream(): + AVStreamWrapper() + { + this->frames_per_second = 0.0; + } + + double frames_per_second; +}; + +static bool av_open_stream(AVFormatContext *format_ctx, AVMediaType type, AVStreamWrapper *out_stream) +{ + int stream_idx = av_find_best_stream(format_ctx, type, -1, -1, nullptr, 0); + if (stream_idx >= 0) + { + 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->type = type; + out_stream->stream_idx = stream_idx; + out_stream->stream = stream; + out_stream->dec = dec; + out_stream->dec_ctx = dec_ctx; + return true; + } + } + } + return false; +} + +static bool av_open_audio_stream(AVFormatContext *format_ctx, AVAudioStream *out_stream) +{ + if (!av_open_stream(format_ctx, AVMEDIA_TYPE_AUDIO, out_stream)) + return Error("Audio stream not found"), false; + + // we support only 2-channel audio for now + if (out_stream->dec_ctx->channels != 2) + { + out_stream->Release(); + return Error("Unsupported number of channels: %u", out_stream->dec_ctx->channels), false; + } + + out_stream->bytes_per_sample = av_num_bytes_per_sample(out_stream->dec_ctx->sample_fmt); + out_stream->bytes_per_second = out_stream->dec_ctx->channels * out_stream->dec_ctx->sample_rate * out_stream->bytes_per_sample; + + return true; +} + +static bool av_open_video_stream(AVFormatContext *format_ctx, AVVideoStream *out_stream) +{ + if (!av_open_stream(format_ctx, AVMEDIA_TYPE_VIDEO, out_stream)) + return Error("Video stream not found"), false; + + out_stream->frames_per_second = (double)out_stream->dec_ctx->time_base.den / (double)out_stream->dec_ctx->time_base.num; + return true; +} + + + +void InterleaveAudioData(MemoryStream *stream, AVSampleFormat src_format, int num_channels, int num_samples, uint8_t **channels) +{ + unsigned int bytes_per_sample; + switch (src_format) + { + case AV_SAMPLE_FMT_U8: + case AV_SAMPLE_FMT_U8P: + __debugbreak(); + + case AV_SAMPLE_FMT_S16: + bytes_per_sample = sizeof(__int16); + stream->Write(channels[0], num_channels * num_samples * bytes_per_sample); + break; + + case AV_SAMPLE_FMT_S16P: + { + bytes_per_sample = sizeof(__int16); + for (int i = 0; i < num_samples; ++i) + for (int j = 0; j < num_channels; ++j) + stream->Write(channels[j] + i * bytes_per_sample, bytes_per_sample); + } + break; + + case AV_SAMPLE_FMT_FLT: + { + SwrContext *converter = swr_alloc(); + av_opt_set_int(converter, "in_channel_layout", av_get_default_channel_layout(2), 0); + //av_opt_set_int(converter, "in_sample_rate", sample_ra, 0); + av_opt_set_sample_fmt(converter, "in_sample_fmt", AV_SAMPLE_FMT_FLT, 0); + + av_opt_set_int(converter, "out_channel_layout", av_get_default_channel_layout(2), 0); + //av_opt_set_int(converter, "out_sample_rate", dst_sample_rate, 0); + av_opt_set_sample_fmt(converter, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0); + + if (swr_init(converter) < 0) + { + __debugbreak(); + swr_free(&converter); + return; + } + + uint8_t **dst_channels; + int dst_linesize[8]; + //int dst_nb_channels = av_get_channel_layout_nb_channels(dst_channel_layout); + if (av_samples_alloc_array_and_samples(&dst_channels, dst_linesize, 2, num_channels * num_samples, AV_SAMPLE_FMT_S16, 0) < 0) + { + __debugbreak(); + swr_free(&converter); + return; + } + + if (swr_convert(converter, dst_channels, num_channels * num_samples, (const uint8_t **)channels, num_channels * num_samples) >= 0) + stream->Write(dst_channels[0], num_channels * num_samples * sizeof(__int16)); + else + __debugbreak(); + + av_free(dst_channels[0]); + swr_free(&converter); + } + break; + + default: + __debugbreak(); + //if (Resample(next_frame->avframe, next_frame->avframe->channel_layout, next_frame->avframe->sample_rate, + // av_get_default_channel_layout(2), next_frame->avframe->sample_rate, AV_SAMPLE_FMT_S16P, resampled_data)) + } +} + + + +bool DecodeAudioFrame(AVCodecContext *dec_ctx, AVPacket *avpacket, AVFrame *avframe, MemoryStream *out_audio_data, int *out_num_audio_samples) +{ + volatile int decoded = false; + do + { + if (avcodec_decode_audio4(dec_ctx, avframe, (int *)&decoded, avpacket) < 0) + { + log("Cannot decode audio frame\n"); + return false; + } + + if (!decoded) + log("Cannot decode audio frame in one piece\n"); + } while (!decoded); + + switch (dec_ctx->codec_id) + { + case AV_CODEC_ID_BINKAUDIO_RDFT: + {//pts samples dpts + // 0 960 + //17280 960 17280 18x960 + //18240 960 960 1x960 + //20160 960 1920 2x960 + //21120 960 960 1x960 + //23040 960 1920 2x960 + static int bink_next_pts = 0; + + // there's a gap in the sound - fill empty samples in + if (bink_next_pts < avpacket->pts) + { + short silence[1024]; + memset(silence, 0, sizeof(silence)); + + int samples_to_fill = /*dec_ctx->channels * */(avpacket->pts - bink_next_pts); + while (samples_to_fill > 0) + { + int samples_to_fill_this_step = samples_to_fill >= 1024 ? 1024 : samples_to_fill; + out_audio_data->Write(silence, samples_to_fill_this_step * sizeof(short)); + + samples_to_fill -= samples_to_fill_this_step; + } + } + + bink_next_pts = avpacket->pts + /*dec_ctx->channels * */avframe->nb_samples; + } + break; + /* + case AV_CODEC_ID_SMACKAUDIO: + { + static int smack_debug_next_audio_time = 0; + if (smack_debug_next_audio_time != packet->pts) + { + Error("There's a gap in the sound before frame %u\n", num_audio_frames); + __debugbreak(); // there's a gap in the sound + } + + int num_actual_data_channels = 0; + switch (dec_ctx->sample_fmt) + { + case AV_SAMPLE_FMT_U8: + case AV_SAMPLE_FMT_S16: + case AV_SAMPLE_FMT_S32: + case AV_SAMPLE_FMT_FLT: + case AV_SAMPLE_FMT_DBL: + num_actual_data_channels = 1; + break; + + case AV_SAMPLE_FMT_U8P: + case AV_SAMPLE_FMT_S16P: + case AV_SAMPLE_FMT_S32P: + case AV_SAMPLE_FMT_FLTP: + case AV_SAMPLE_FMT_DBLP: + num_actual_data_channels = dec_ctx->channels; + break; + + default: + case AV_SAMPLE_FMT_NONE: + case AV_SAMPLE_FMT_NB: + __debugbreak(); + } + + smack_debug_next_audio_time += dec_ctx->channels * frame->nb_samples * bytes_per_sample; + Assert(frame->avframe->linesize[0] == audio.dec_ctx->channels * frame->avframe->nb_samples * audio.bytes_per_sample / num_actual_data_channels, + "Smack audio size mismatch in frame %u in %s\n", audio_num_read_frames, movie_filename); + + frame->play_time = (double)frame->avpacket->pts / (double)audio.bytes_per_second; + } + break; + + case AV_CODEC_ID_MP3: + { + static int mp3_samples_decoded_so_far = 0; + static int mp3_prev_samples_count = frame->avframe->nb_samples; // mp3 seems to always feed same amount of samples + frame->play_time = (double)mp3_samples_decoded_so_far / (double)audio.dec_ctx->sample_rate; + + mp3_samples_decoded_so_far += frame->avframe->nb_samples; + Assert(mp3_prev_samples_count == frame->avframe->nb_samples, + "MP3 audio have variable sample count in frame %u in %s\n", audio_num_read_frames, movie_filename); + } + break; + + default: + { + __debugbreak(); + double samples_per_second = (double)audio.dec_ctx->time_base.den / (double)audio.dec_ctx->time_base.num; + double play_length = frame->avframe->nb_samples / samples_per_second; + frame->play_time = (double)frame->avpacket->pts / samples_per_second; + } + break;*/ + } + + if (!avframe->channel_layout) + { + log("Audio channel layout not specified, rolling back to default\n"); + avframe->channel_layout = av_get_default_channel_layout(dec_ctx->channels); + } + + *out_num_audio_samples = dec_ctx->channels * avframe->nb_samples; + InterleaveAudioData(out_audio_data, dec_ctx->sample_fmt, + dec_ctx->channels, avframe->nb_samples, avframe->data); + return true; +} + + +bool LoadAudioTrack(AVFormatContext *format_ctx, AVCodecContext *dec_ctx, int audio_stream_idx, MemoryStream *out_audio_stream, int *out_num_audio_frames, int *out_num_audio_samples) +{ + out_audio_stream->Reset(); + + AVFrame *frame = avcodec_alloc_frame(); + AVPacket *packet = new AVPacket; + av_init_packet(packet); + + int num_audio_frames = 0; + int num_audio_samples = 0; + while (av_read_frame(format_ctx, packet) >= 0) + { + if (packet->stream_index != audio_stream_idx) + { + //log("Suspicious stream id %u in %s", packet->stream_index, filenamea); + continue; + } + + int num_samples_decoded; + DecodeAudioFrame(dec_ctx, packet, frame, out_audio_stream, &num_samples_decoded); + + num_audio_samples += num_samples_decoded; + num_audio_frames++; + } + *out_num_audio_frames = num_audio_frames; + *out_num_audio_samples = num_audio_samples; + + avcodec_free_frame(&frame); + delete packet; + + return true; +} + + +class Track: public Media::ITrack +{ + public: + inline Track() + { + this->format_ctx = nullptr; + this->audio_num_samples = 0; + } + + void Release() + { + ReleaseAvcodec(); + } + + void ReleaseAvcodec() + { + audio.Release(); + if (format_ctx) + { + av_close_input_file(format_ctx); + format_ctx = nullptr; + } + } + + bool Load(const wchar_t *filename) + { + char filenamea[1024]; + sprintf(filenamea, "%S", filename); + + if (avformat_open_input(&format_ctx, filenamea, nullptr, nullptr) >= 0) + { + if (avformat_find_stream_info(format_ctx, nullptr) >= 0) + { + av_dump_format(format_ctx, 0, filenamea, 0); + + if (!av_open_audio_stream(format_ctx, &audio)) + { + Error("Cannot open strack: %s", filenamea); + return Release(), false; + } + + MemoryStream audio_plain_data; + int num_audio_frames; + int num_audio_samples; + if (LoadAudioTrack(format_ctx, audio.dec_ctx, audio.stream_idx, &audio_plain_data, &num_audio_frames, &num_audio_samples)) + { + /*#ifdef _DEBUG + char debug_filename[1024]; + sprintf(debug_filename, "%s.wav", filenamea); + FILE *wav = fopen(debug_filename, "w+b"); + + extern void write_wav_header(FILE *wav, int channel_count = 2, int sample_rate = 22050, int bytes_per_sample = 2); + write_wav_header(wav, audio.dec_ctx->channels, audio.dec_ctx->sample_rate, audio.bytes_per_sample); + + fwrite(audio_plain_data.Ptr(), audio_plain_data.Current(), 1, wav); + + extern void fix_wav_header(FILE *wav, int wav_bytes_in_stream); + fix_wav_header(wav, audio_plain_data.Current()); + #endif*/ + + device_buffer = provider->CreateTrack16(audio.dec_ctx->channels, audio.dec_ctx->sample_rate, 2, num_audio_samples, audio_plain_data.Ptr()); + + ReleaseAvcodec(); + return true; + } + } + Release(); + } + return false; + } + + virtual void Play(bool loop) + { + provider->PlayTrack16(device_buffer, loop); + } + + + protected: + AVFormatContext *format_ctx; + AVAudioStream audio; + int audio_num_samples; + + OpenALSoundProvider::TrackBuffer *device_buffer; +}; + + + +class Movie: public Media::IMovie +{ + public: + inline Movie() + { + this->movie_filename[0] = 0; + this->width = 0; + this->height = 0; + this->format_ctx = nullptr; + this->end_of_file = false; + this->playback_time = 0.0; + + this->num_audio_frames = 0; + this->num_audio_samples = 0; + + this->last_resampled_frame_num = -1; + memset(last_resampled_frame_data, 0, sizeof(last_resampled_frame_data)); + memset(last_resampled_frame_linesize, 0, sizeof(last_resampled_frame_linesize)); + } + + + inline void Release() + { + ReleaseAVCodec(); + } + + inline void ReleaseAVCodec() + { + audio.Release(); + video.Release(); + if (format_ctx) + { + av_close_input_file(format_ctx); + format_ctx = nullptr; + } + } + + bool Load(const wchar_t *filename, int dst_width, int dst_height, int cache_ms) + { + char filenamea[1024]; + sprintf(filenamea, "%S", filename); + sprintf(movie_filename, "%S", filename); + + width = dst_width; + height = dst_height; + if (avformat_open_input(&format_ctx, filenamea, nullptr, nullptr) >= 0) + { + if (avformat_find_stream_info(format_ctx, nullptr) >= 0) + { + av_dump_format(format_ctx, 0, filenamea, 0); + + if (!av_open_audio_stream(format_ctx, &audio)) + { + Error("Cannot open audio stream: %s", filenamea); + return Release(), false; + } + + if (!av_open_video_stream(format_ctx, &video)) + { + Error("Cannot open video stream: %s", filenamea); + return Release(), false; + } + + decoding_packet = new AVPacket; + av_init_packet(decoding_packet); + + decoding_frame = avcodec_alloc_frame(); + + audio_data_in_device = provider->CreateStreamingTrack16(audio.dec_ctx->channels, audio.dec_ctx->sample_rate, 2); + + return true; + } + } + return false; + } + + virtual void Play() + { + } + + virtual void GetNextFrame(double dt, void *dst_surface) + { + playback_time += dt; + + AVPacket *avpacket = decoding_packet; + AVFrame *avframe = decoding_frame; + avcodec_get_frame_defaults(avframe); + + int desired_frame_number = floor(playback_time * video.dec_ctx->time_base.den / video.dec_ctx->time_base.num + 0.5); + if (last_resampled_frame_num == desired_frame_number) + { + memcpy(dst_surface, last_resampled_frame_data[0], height * last_resampled_frame_linesize[0]); + return; + } + + volatile int decoded = false; + do + { + if (av_read_frame(format_ctx, avpacket) < 0) + { + // probably movie is finished + __debugbreak(); + } + + // audio packet - queue into playing + if (avpacket->stream_index == audio.stream_idx) + { + MemoryStream audio_data; + if (DecodeAudioFrame(audio.dec_ctx, avpacket, avframe, &audio_data, &num_audio_samples)) + provider->Stream16(audio_data_in_device, num_audio_samples, audio_data.Ptr()); + } + // video packet - decode & maybe show + else if (avpacket->stream_index == video.stream_idx) + { + do + { + if (avcodec_decode_video2(video.dec_ctx, avframe, (int *)&decoded, avpacket) < 0) + __debugbreak(); + } while (!decoded); + } + + } while (avpacket->stream_index != video.stream_idx || + avpacket->pts != desired_frame_number); + + if (decoded) + { + if (last_resampled_frame_data[0]) + av_freep(&last_resampled_frame_data[0]); + + AVPixelFormat rescaled_format = AV_PIX_FMT_RGB32; + uint8_t *rescaled_data[4] = {nullptr, nullptr, nullptr, nullptr}; + int rescaled_linesize[4] = {0, 0, 0, 0}; + if (av_image_alloc(rescaled_data, rescaled_linesize, width, height, rescaled_format, 1) >= 0) + { + SwsContext *converter = sws_getContext(avframe->width, avframe->height, (AVPixelFormat)avframe->format, + width, height, rescaled_format, + SWS_BICUBIC, nullptr, nullptr, nullptr); + sws_scale(converter, avframe->data, avframe->linesize, 0, avframe->height, rescaled_data, rescaled_linesize); + sws_freeContext(converter); + + memcpy(dst_surface, rescaled_data[0], height * rescaled_linesize[0]); + + last_resampled_frame_num = desired_frame_number; + memcpy(last_resampled_frame_data, rescaled_data, sizeof(rescaled_data)); + memcpy(last_resampled_frame_linesize, rescaled_linesize, sizeof(rescaled_linesize)); + } + } + else + memset(dst_surface, 0, width * height * 4); + } + + protected: + char movie_filename[256]; + int width; + int height; + AVFormatContext *format_ctx; + double playback_time; + bool end_of_file; + + AVPacket *decoding_packet; + AVFrame *decoding_frame; + + AVAudioStream audio; + int num_audio_frames; + int num_audio_samples; + OpenALSoundProvider::StreamingTrackBuffer *audio_data_in_device; + + AVVideoStream video; + int last_resampled_frame_num; + uint8_t *last_resampled_frame_data[4]; + int last_resampled_frame_linesize[4]; +}; + + +ITrack *Player::LoadTrack(const wchar_t *filename) +{ + auto track = new Track; + if (!track->Load(filename)) + { + delete track; + track = nullptr; + } + return track; +} + + +IMovie *Player::LoadMovie(const wchar_t *filename, int width, int height, int cache_ms) +{ + auto movie = new Movie; + if (!movie->Load(filename, width, height, cache_ms)) + { + delete movie; + movie = nullptr; + } + return movie; +} + + + + + + +void av_logger(void *, int, const char *format, va_list args) +{ + va_list va; + va_start(va, format); + char msg[256]; + vsprintf(msg, format, va); + va_end(va); + + log("av: %s", msg); +} + +Player::Player() +{ + static int libavcodec_initialized = false; + + if (!libavcodec_initialized) + { + av_log_set_callback(av_logger); + avcodec_register_all(); + av_register_all(); + + libavcodec_initialized = true; + } + + if (!provider) + { + provider = new OpenALSoundProvider; + provider->Initialize(); + } +} + +Player::~Player() +{ +} \ No newline at end of file