Parcourir la source

#IQBX #comment mediaplayer 编译通过

80374374 il y a 1 an
Parent
commit
f9c627da83

+ 1 - 0
Other/win/CMakeLists.txt

@@ -12,6 +12,7 @@ add_subdirectory(libbizchan)
 
 add_subdirectory(libimgplayer)
 add_subdirectory(libwmpplayer)
+add_subdirectory(libmediaplayer)
 
 # 汇总要依赖拷贝的第三方库
 set(RVC_CONAN_DEP_LIBS ${RVC_CONAN_DEP_LIBS} ${OTHER_CONAN_DEP_LIBS} PARENT_SCOPE)

+ 74 - 0
Other/win/libmediaplayer/CMakeLists.txt

@@ -0,0 +1,74 @@
+set(MODULE_NAME "mediaplayer")
+set(MODULE_PREFIX "LIB_MEDIAPLAYER_FUNC")
+
+add_definitions(-D__STDC_CONSTANT_MACROS)
+
+set(${MODULE_PREFIX}_SRCS
+	libmediaplayer.h
+	libmediaplayer.cpp
+	player.h
+	player.cpp
+	audio.h
+	audio.cpp
+	demux.h
+	demux.cpp
+	frame.h
+	frame.cpp
+	packet.h
+	packet.cpp
+	video.h
+	video.cpp
+	dllmain.cpp
+)
+
+add_library(${MODULE_NAME} SHARED ${${MODULE_PREFIX}_SRCS})
+
+
+
+target_include_directories(${MODULE_NAME} PRIVATE
+	${RVC_COMMON_INCLUDE_DIR}
+	${CONAN_INCLUDE_DIRS_FFMPEG}
+	${CONAN_INCLUDE_DIRS_SDL2}
+	)
+
+
+target_link_directories(${MODULE_NAME} PRIVATE
+	${CONAN_LIB_DIRS_FFMPEG}
+	${CONAN_LIB_DIRS_SDL2}
+	)
+
+
+message(STATUS "CONAN_LIBS_FFMPEG== ${CONAN_LIBS_FFMPEG}")
+message(STATUS "CONAN_INCLUDE_DIRS_FFMPEG== ${CONAN_INCLUDE_DIRS_FFMPEG}")
+message(STATUS "CONAN_PKG_LIBS_FFMPEG== ${CONAN_PKG_LIBS_FFMPEG}")
+message(STATUS "CONAN_LIBS_SDL2 == ${CONAN_LIBS_SDL2}")
+
+target_link_libraries(${MODULE_NAME} PRIVATE ${${MODULE_PREFIX}_LIBS} PRIVATE
+	${CONAN_LIBS_FFMPEG}
+	${CONAN_LIBS_SDL2}
+	)  
+
+target_compile_definitions(${MODULE_NAME} PUBLIC "LIBMEDIAPLAYER_EXPORTS")
+
+
+
+if(MSVC)
+	install(TARGETS ${MODULE_NAME} 
+    RUNTIME DESTINATION "${RVC_RUNTIME_PATH}" COMPONENT libraries
+    ARCHIVE DESTINATION "${RVC_LIBRARY_PATH}" COMPONENT develops EXCLUDE_FROM_ALL
+    LIBRARY DESTINATION "${RVC_LIBRARY_PATH}" COMPONENT libraries
+    )
+else()
+install(TARGETS ${MODULE_NAME} 
+    RUNTIME DESTINATION "${RVC_RUNTIME_PATH}"
+    ARCHIVE DESTINATION "${RVC_LIBRARY_PATH}"
+    LIBRARY DESTINATION "${RVC_RUNTIME_PATH}"
+    COMPONENT libraries)
+endif(MSVC)
+
+# ����Ҫ���������ĵ�������
+if(MSVC)
+set(OTHER_CONAN_DEP_LIBS ${OTHER_CONAN_DEP_LIBS} ${CONAN_BIN_DIRS} PARENT_SCOPE)
+else()
+set(OTHER_CONAN_DEP_LIBS ${OTHER_CONAN_DEP_LIBS} ${CONAN_LIB_DIRS} PARENT_SCOPE)
+ENDIF(MSVC)

+ 48 - 0
Other/win/libmediaplayer/ReadMe.txt

@@ -0,0 +1,48 @@
+========================================================================
+    DYNAMIC LINK LIBRARY : libmediaplayer Project Overview
+========================================================================
+
+AppWizard has created this libmediaplayer DLL for you.
+
+This file contains a summary of what you will find in each of the files that
+make up your libmediaplayer application.
+
+
+libmediaplayer.vcxproj
+    This is the main project file for VC++ projects generated using an Application Wizard.
+    It contains information about the version of Visual C++ that generated the file, and
+    information about the platforms, configurations, and project features selected with the
+    Application Wizard.
+
+libmediaplayer.vcxproj.filters
+    This is the filters file for VC++ projects generated using an Application Wizard. 
+    It contains information about the association between the files in your project 
+    and the filters. This association is used in the IDE to show grouping of files with
+    similar extensions under a specific node (for e.g. ".cpp" files are associated with the
+    "Source Files" filter).
+
+libmediaplayer.cpp
+    This is the main DLL source file.
+
+	When created, this DLL does not export any symbols. As a result, it
+	will not produce a .lib file when it is built. If you wish this project
+	to be a project dependency of some other project, you will either need to
+	add code to export some symbols from the DLL so that an export library
+	will be produced, or you can set the Ignore Input Library property to Yes
+	on the General propert page of the Linker folder in the project's Property
+	Pages dialog box.
+
+/////////////////////////////////////////////////////////////////////////////
+Other standard files:
+
+StdAfx.h, StdAfx.cpp
+    These files are used to build a precompiled header (PCH) file
+    named libmediaplayer.pch and a precompiled types file named StdAfx.obj.
+
+/////////////////////////////////////////////////////////////////////////////
+Other notes:
+
+AppWizard uses "TODO:" comments to indicate parts of the source code you
+should add to or customize.
+
+/////////////////////////////////////////////////////////////////////////////

+ 564 - 0
Other/win/libmediaplayer/audio.cpp

@@ -0,0 +1,564 @@
+#include "player.h"
+#include "packet.h"
+#include "frame.h"
+#include "Windows.h"
+
+static void sdl_audio_callback(void *opaque, uint8_t*stream, int len);
+
+// 从packet_queue中取一个packet,解码生成frame
+static int audio_decode_frame(AVCodecContext *p_codec_ctx, packet_queue_t *p_pkt_queue, AVFrame *frame, CMediaHostApi* hostapi)
+{
+    int ret = -1;
+	if (NULL == p_codec_ctx){
+		return ret;
+	}
+
+    while (0 == p_pkt_queue->abort_flag)
+    {
+		AVPacket pkt = {0};
+
+        while (0 == p_pkt_queue->abort_flag)
+        {
+            // 3.2 一个音频packet含一至多个音频frame,每次avcodec_receive_frame()返回一个frame,此函数返回。
+            // 下次进来此函数,继续获取一个frame,直到avcodec_receive_frame()返回AVERROR(EAGAIN),
+            // 表示解码器需要填入新的音频packet
+            ret = avcodec_receive_frame(p_codec_ctx, frame);
+            if (ret >= 0)
+            {
+                // 时基转换,从d->avctx->pkt_timebase时基转换到1/frame->sample_rate时基
+				AVRational tb = { 1, frame->sample_rate };
+                if (frame->pts != AV_NOPTS_VALUE)
+                {
+                    frame->pts = av_rescale_q(frame->pts, p_codec_ctx->pkt_timebase, tb);
+                }
+                else
+                {
+					//hostapi->Debug(MEDIA_LOG_DEBUG, "frame->pts no.");
+                }
+
+                return 1;
+            }
+            else if (ret == AVERROR_EOF)
+            {
+                avcodec_flush_buffers(p_codec_ctx);
+                return -1;
+            }
+            else if (ret == AVERROR(EAGAIN))
+            {
+				break;
+            }
+            else
+            {
+				hostapi->Debug(MEDIA_LOG_DEBUG, "audio avcodec_receive_frame(): other errors.");
+				continue;
+            }
+        }
+
+        // 1. 取出一个packet。使用pkt对应的serial赋值给d->pkt_serial
+        if (packet_queue_get(p_pkt_queue, &pkt, true, hostapi) < 0)
+        {
+			hostapi->Debug(MEDIA_LOG_DEBUG, "packet_queue_get return -1 exit audio_decode_frame function.");
+            return -1;
+        }
+
+        // packet_queue中第一个总是flush_pkt。每次seek操作会插入flush_pkt,更新serial,开启新的播放序列
+        if (NULL == pkt.data || 0 == pkt.size)
+        {
+            // 复位解码器内部状态/刷新内部缓冲区。当seek操作或切换流时应调用此函数。
+            avcodec_flush_buffers(p_codec_ctx);
+			return -2;
+        }
+        else
+        {
+            // 2. 将packet发送给解码器
+            //    发送packet的顺序是按dts递增的顺序,如IPBBPBB
+            //    pkt.pos变量可以标识当前packet在视频文件中的地址偏移
+			int iresult = avcodec_send_packet(p_codec_ctx, &pkt);
+            if (AVERROR(EAGAIN) == iresult)
+            {
+				hostapi->Debug(MEDIA_LOG_DEBUG, "receive_frame and send_packet both returned EAGAIN, which is an API violation.");
+            }
+			if (0 == iresult)
+			{
+				av_packet_unref(&pkt);
+			}
+        }
+    }
+
+	return ret;
+}
+
+// 音频解码线程:从音频packet_queue中取数据,解码后放入音频frame_queue
+static int audio_decode_thread(void *arg)
+{
+    player_stat_t *is = (player_stat_t *)arg;
+    AVFrame *p_frame = av_frame_alloc();
+    frame_t *af;
+
+    int got_frame = 0;
+    AVRational tb;
+    int ret = 0;
+
+    if (p_frame == NULL){
+        return AVERROR(ENOMEM);
+    }
+
+	while (false == is->buser_stop)
+    {
+        got_frame = audio_decode_frame(is->p_acodec_ctx[is->iaudio_dec_index], &is->audio_pkt_queue, p_frame, is->rvc_hostapi);
+        if (got_frame < 0){
+			if(-2 == got_frame){
+				if (is->icurrent_index > is->iaudio_dec_index) {
+					is->iaudio_dec_index++;
+				}
+				continue;
+			}
+			else {
+				is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, " audio_decode_frame < 0, goto end");
+				goto the_end;
+			}
+		}
+
+        if (got_frame)
+        {
+			//tb = { 1, p_frame->sample_rate };
+			tb.num = 1;
+			tb.den = p_frame->sample_rate;
+			//从frame队列找到一个可写的空间,若未停止则一直等待,已停止时返回NULL
+			if (!(af = frame_queue_peek_writable(&is->audio_frm_queue))) {
+				is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "frame_queue_peek_writable return NULL, goto end.");
+				goto the_end;
+			}
+            af->pts = (p_frame->pts == AV_NOPTS_VALUE) ? NAN : p_frame->pts * av_q2d(tb);
+            af->pos = p_frame->pkt_pos;
+            //-af->serial = is->auddec.pkt_serial;
+            // 当前帧包含的(单个声道)采样数/采样率就是当前帧的播放时长
+			AVRational tbdata = { p_frame->nb_samples, p_frame->sample_rate };
+            //af->duration = av_q2d((AVRational) { p_frame->nb_samples, p_frame->sample_rate });
+			af->duration = av_q2d(tbdata);
+            // 将frame数据拷入af->frame,af->frame指向音频frame队列尾部
+            av_frame_move_ref(af->frame, p_frame);
+            // 更新音频frame队列大小及写指针
+            frame_queue_push(&is->audio_frm_queue);
+        }
+		//av_usleep(RVC_DEFAULT_SLEEP_TIME);
+    }
+
+the_end:
+    av_frame_free(&p_frame);
+	is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "audio decode thread exit, thread id is %u, and user stop flag is %s.", SDL_ThreadID(), is->buser_stop ? "true":"false");
+	is->baudio_decode_finished = true;
+
+	return ret;
+}
+
+int open_audio_stream(player_stat_t *is)
+{
+    AVCodecContext *p_codec_ctx = NULL;
+    AVCodecParameters *p_codec_par = NULL;
+    AVCodec* p_codec = NULL;
+    int ret = -1;
+
+    // 1. 为音频流构建解码器AVCodecContext
+	for (size_t index = 0; index < is->uFilesCount; index++){
+		// 1.1 获取解码器参数AVCodecParameters
+
+		p_codec_par = is->p_audio_stream[index]->codecpar;
+		// 1.2 获取解码器
+		p_codec = avcodec_find_decoder(p_codec_par->codec_id);
+		if (NULL == p_codec) {
+			is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "Cann't find codec!");
+			return ret;
+		}
+
+		// 1.3 构建解码器AVCodecContext
+		// 1.3.1 p_codec_ctx初始化:分配结构体,使用p_codec初始化相应成员为默认值
+		p_codec_ctx = avcodec_alloc_context3(p_codec);
+		if (p_codec_ctx == NULL) {
+			is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "avcodec_alloc_context3() failed.");
+			return ret;
+		}
+		// 1.3.2 p_codec_ctx初始化:p_codec_par ==> p_codec_ctx,初始化相应成员
+		ret = avcodec_parameters_to_context(p_codec_ctx, p_codec_par);
+		if (ret < 0) {
+			is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "avcodec_parameters_to_context() failed %d.", ret);
+			return ret;
+		}
+		// 1.3.3 p_codec_ctx初始化:使用p_codec初始化p_codec_ctx,初始化完成
+		ret = avcodec_open2(p_codec_ctx, p_codec, NULL);
+		if (ret < 0) {
+			is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "avcodec_open2() failed %d.", ret);
+			return ret;
+		}
+
+		p_codec_ctx->pkt_timebase = is->p_audio_stream[index]->time_base;
+		is->p_acodec_ctx[index] = p_codec_ctx;
+		is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "%s:%d is->p_acodec_ctx[%d] = 0x%08x", __FUNCTION__, __LINE__, index, p_codec_ctx);
+	}
+
+    // 2. 创建音频解码线程
+	is->audio_decode_tid = SDL_CreateThread(audio_decode_thread, "audio decode thread", is);
+	if (NULL == is->audio_decode_tid) {
+		is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "SDL_Create audio decode thread failed: %s.", SDL_GetError());
+		return -1;
+	}
+	else {
+		ret = 0;
+		is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "create %s success, and thread id is %u.", SDL_GetThreadName(is->audio_decode_tid), SDL_GetThreadID(is->audio_decode_tid));
+	}
+
+    return ret;
+}
+
+static int audio_resample(player_stat_t *is, int64_t audio_callback_time)
+{
+    int data_size = 0, resampled_data_size = 0;
+    int64_t dec_channel_layout = 0;
+    av_unused double audio_clock0 = 0.0;
+    int wanted_nb_samples = 0;
+    frame_t *af = NULL;
+
+    while (frame_queue_nb_remaining(&is->audio_frm_queue) == 0){
+		if ((av_gettime_relative() - audio_callback_time) > 1000000LL * is->audio_hw_buf_size / is->audio_param_tgt.bytes_per_sec / 2) {
+			return -1;
+		}
+        av_usleep(1000);
+    }
+
+	//is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "%s:%d.", __FUNCTION__, __LINE__);
+    // 若队列头部可读,则由af指向可读帧
+	if (!(af = frame_queue_peek_readable(&is->audio_frm_queue))) {
+		is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "%s:%d user stop flag is %s, function return", __FUNCTION__, __LINE__, is->buser_stop ? "true" : "false");
+		return -1;
+	}
+    frame_queue_next(&is->audio_frm_queue);
+
+    // 根据frame中指定的音频参数获取缓冲区的大小
+    data_size = av_samples_get_buffer_size(NULL, af->frame->channels,    // 本行两参数:linesize,声道数
+        af->frame->nb_samples,											 // 本行一参数:本帧中包含的单个声道中的样本数
+        (AVSampleFormat)af->frame->format, 1);							 // 本行两参数:采样格式,不对齐
+
+// 获取声道布局
+    dec_channel_layout =
+        (af->frame->channel_layout && af->frame->channels == av_get_channel_layout_nb_channels(af->frame->channel_layout)) ?
+        af->frame->channel_layout : av_get_default_channel_layout(af->frame->channels);
+    wanted_nb_samples = af->frame->nb_samples;
+
+    // is->audio_param_tgt是SDL可接受的音频帧数,是audio_open()中取得的参数
+    // 在audio_open()函数中又有“is->audio_src = is->audio_param_tgt”
+    // 此处表示:如果frame中的音频参数 == is->audio_src == is->audio_param_tgt,那音频重采样的过程就免了(因此时is->swr_ctr是NULL)
+    //      否则使用frame(源)和is->audio_param_tgt(目标)中的音频参数来设置is->swr_ctx,并使用frame中的音频参数来赋值is->audio_src
+    if (af->frame->format != is->audio_param_src.fmt ||
+        dec_channel_layout != is->audio_param_src.channel_layout ||
+        af->frame->sample_rate != is->audio_param_src.freq)
+    {
+        swr_free(&is->audio_swr_ctx);
+        // 使用frame(源)和is->audio_param_tgt(目标)中的音频参数来设置is->audio_swr_ctx
+        is->audio_swr_ctx = swr_alloc_set_opts(NULL,
+            is->audio_param_tgt.channel_layout, (AVSampleFormat)is->audio_param_tgt.fmt, is->audio_param_tgt.freq,
+            dec_channel_layout, (AVSampleFormat)af->frame->format, af->frame->sample_rate,
+            0, NULL);
+        if (!is->audio_swr_ctx || swr_init(is->audio_swr_ctx) < 0)
+        {
+			is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "Cannot create sample rate converter for conversion of %d Hz %s %d channels to %d Hz %s %d channels!",
+				af->frame->sample_rate, av_get_sample_fmt_name((AVSampleFormat)af->frame->format), af->frame->channels,
+				is->audio_param_tgt.freq, av_get_sample_fmt_name((AVSampleFormat)is->audio_param_tgt.fmt), is->audio_param_tgt.channels);
+            swr_free(&is->audio_swr_ctx);
+            return -1;
+        }
+        // 使用frame中的参数更新is->audio_src,第一次更新后后面基本不用执行此if分支了,因为一个音频流中各frame通用参数一样
+        is->audio_param_src.channel_layout = dec_channel_layout;
+        is->audio_param_src.channels = af->frame->channels;
+        is->audio_param_src.freq = af->frame->sample_rate;
+        is->audio_param_src.fmt = (AVSampleFormat)af->frame->format;
+    }
+	
+    if (is->audio_swr_ctx)
+    {
+        // 重采样输入参数1:输入音频样本数是af->frame->nb_samples
+        // 重采样输入参数2:输入音频缓冲区
+        const uint8_t **in = (const uint8_t **)af->frame->extended_data;
+        // 重采样输出参数1:输出音频缓冲区尺寸
+        // 重采样输出参数2:输出音频缓冲区
+        uint8_t **out = &is->audio_frm_rwr;
+        // 重采样输出参数:输出音频样本数(多加了256个样本)
+        int out_count = (int64_t)wanted_nb_samples * is->audio_param_tgt.freq / af->frame->sample_rate + 256;
+        // 重采样输出参数:输出音频缓冲区尺寸(以字节为单位)
+        int out_size = av_samples_get_buffer_size(NULL, is->audio_param_tgt.channels, out_count, (AVSampleFormat)is->audio_param_tgt.fmt, 0);
+        int len2 = 0;
+        if (out_size < 0){
+			is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "av_samples_get_buffer_size() failed.");
+            return -1;
+        }
+        av_fast_malloc(&is->audio_frm_rwr, &is->audio_frm_rwr_size, out_size);
+		if (!is->audio_frm_rwr) {
+			return AVERROR(ENOMEM);
+		}
+        // 音频重采样:返回值是重采样后得到的音频数据中单个声道的样本数
+        len2 = swr_convert(is->audio_swr_ctx, out, out_count, in, af->frame->nb_samples);
+        if (len2 < 0){
+			is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "swr_convert() failed.");
+            return -1;
+        }
+
+        if (len2 == out_count){
+			is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "audio buffer is probably too small.");
+			if (swr_init(is->audio_swr_ctx) < 0)
+                swr_free(&is->audio_swr_ctx);
+        }
+        is->p_audio_frm = is->audio_frm_rwr;
+        // 重采样返回的一帧音频数据大小(以字节为单位)
+        resampled_data_size = len2 * is->audio_param_tgt.channels * av_get_bytes_per_sample((AVSampleFormat)is->audio_param_tgt.fmt);
+    }
+    else
+    {
+        // 未经重采样,则将指针指向frame中的音频数据
+        is->p_audio_frm = af->frame->data[0];
+        resampled_data_size = data_size;
+    }
+
+    audio_clock0 = is->audio_clock;
+    /* update the audio clock with the pts */
+    if (!isnan(af->pts)){
+        is->audio_clock = af->pts + (double)af->frame->nb_samples / af->frame->sample_rate;
+    }
+    else{
+        is->audio_clock = NAN;
+    }
+    is->audio_clock_serial = af->serial;
+#ifdef DEBUG
+    {
+        static double last_clock;
+        //printf("audio: delay=%0.3f clock=%0.3f clock0=%0.3f\n",
+        //    is->audio_clock - last_clock,
+        //    is->audio_clock, audio_clock0);
+		//is->rvc_log("audio: delay=%0.3f clock=%0.3f clock0=%0.3f\n",
+		//	is->audio_clock - last_clock,
+		//	is->audio_clock, audio_clock0);
+        last_clock = is->audio_clock;
+    }
+#endif
+    return resampled_data_size;
+}
+
+static char* Utf8ToGB2312(const char* utf8)
+{
+	int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);
+
+	wchar_t* wstr = new wchar_t[len + 1];
+	memset(wstr, 0, len + 1);
+
+	MultiByteToWideChar(CP_UTF8, 0, utf8, -1, wstr, len);
+	len = WideCharToMultiByte(CP_ACP, 0, wstr, -1, NULL, 0, NULL, NULL);
+
+	char* str = new char[len + 1];
+	memset(str, 0, len + 1);
+
+	WideCharToMultiByte(CP_ACP, 0, wstr, -1, str, len, NULL, NULL);
+
+	if (wstr) {
+		delete []wstr;
+		wstr = NULL;
+	}
+	return str;
+}
+
+
+static int open_audio_playing(void *arg)
+{
+    player_stat_t *is = (player_stat_t *)arg;
+	SDL_AudioSpec wanted_spec = {0};
+	SDL_AudioSpec actual_spec = {0};
+
+    wanted_spec.freq = is->p_acodec_ctx[is->iaudio_dec_index]->sample_rate;   // 采样率
+    wanted_spec.format = AUDIO_S16SYS;                  // S表带符号,16是采样深度,SYS表采用系统字节序
+    wanted_spec.channels = is->p_acodec_ctx[is->iaudio_dec_index]->channels;  // 声音通道数
+    wanted_spec.silence = 0;                            // 静音值
+    // wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE;     // SDL声音缓冲区尺寸,单位是单声道采样点尺寸x通道数
+    // SDL声音缓冲区尺寸,单位是单声道采样点尺寸x声道数
+    wanted_spec.samples = FFMAX(SDL_AUDIO_MIN_BUFFER_SIZE, 2 << av_log2(wanted_spec.freq / SDL_AUDIO_MAX_CALLBACKS_PER_SEC));
+	wanted_spec.callback = sdl_audio_callback;          // 回调函数,若为NULL,则应使用SDL_QueueAudio()机制
+    wanted_spec.userdata = is;                          // 提供给回调函数的参数
+	
+	if (NULL == is->straudiodev) {
+		int iaudioapeaker = SDL_GetNumAudioDevices(0);
+		is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "audio output device number is %d.", iaudioapeaker);
+		int i = 0;
+		for (; i < iaudioapeaker; i++) {
+			char* strdevice = Utf8ToGB2312(SDL_GetAudioDeviceName(i, 0));
+			is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "device id %d audio device name is %s.", i, strdevice);
+			if (is->paudiodev && strstr(strdevice, is->paudiodev)) {
+				const char* strdevname = SDL_GetAudioDeviceName(i, 0);
+				is->straudiodev = av_strdup(strdevname);
+				is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "%s matched audio device name is %s.", is->paudiodev, strdevice);
+				delete []strdevice;
+				break;
+			}
+			else {
+				delete[] strdevice;
+			}
+		}
+		if (i == iaudioapeaker) {
+			is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "matched audio device name (%s) failed!", is->straudiodev ? is->straudiodev : "null");
+		}
+
+		{
+			int inum = SDL_GetNumAudioDrivers();
+			int i = 0;
+			is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "Audio Drivers number is %d.", inum);
+			for (; i < inum; i++) {
+				const char* drivername = SDL_GetAudioDriver(i);
+				is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "[%d] Audio Drivers name is %s.", i, drivername);
+			}
+		}
+		is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "current audio driver name is %s.", SDL_GetCurrentAudioDriver());
+	}
+
+	while (!(audio_dev = SDL_OpenAudioDevice(is->straudiodev, 0, &wanted_spec, &actual_spec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | SDL_AUDIO_ALLOW_CHANNELS_CHANGE))){
+		is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "SDL_OpenAudio (%d channels, %d Hz): %s",wanted_spec.channels, wanted_spec.freq, SDL_GetError());
+		if (!wanted_spec.channels) {
+			if (!wanted_spec.freq) {
+				is->rvc_hostapi->Debug(MEDIA_LOG_ERROR,"No more combinations to try, audio open failed!");
+				return -1;
+			}
+		}
+		return -1;
+	}
+
+	is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "SDL_OpenAudioDevice success and audio_dev is %d.", audio_dev);
+    is->audio_param_tgt.fmt = AV_SAMPLE_FMT_S16;
+    is->audio_param_tgt.freq = actual_spec.freq;
+    is->audio_param_tgt.channel_layout = av_get_default_channel_layout(actual_spec.channels);
+    is->audio_param_tgt.channels = actual_spec.channels;
+    is->audio_param_tgt.frame_size = av_samples_get_buffer_size(NULL, actual_spec.channels, 1, (AVSampleFormat)is->audio_param_tgt.fmt, 1);
+    is->audio_param_tgt.bytes_per_sec = av_samples_get_buffer_size(NULL, actual_spec.channels, actual_spec.freq, (AVSampleFormat)is->audio_param_tgt.fmt, 1);
+	
+	is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "audio param target (%d channels, %d Hz, channel_layout(%d), frame_size(%d), bytes_per_sec(%d)).", actual_spec.channels, actual_spec.freq, is->audio_param_tgt.channel_layout, is->audio_param_tgt.frame_size, is->audio_param_tgt.bytes_per_sec);
+
+	if (is->audio_param_tgt.bytes_per_sec <= 0 || is->audio_param_tgt.frame_size <= 0){
+		is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "av_samples_get_buffer_size failed.");
+		SDL_CloseAudioDevice(audio_dev);
+		return -1;
+    }
+    is->audio_param_src = is->audio_param_tgt;
+    is->audio_hw_buf_size = actual_spec.size;   // SDL音频缓冲区大小
+    is->audio_frm_size = 0;
+    is->audio_cp_index = 0;
+
+	SDL_PauseAudioDevice(audio_dev, 0);
+
+	SDL_LockMutex(is->audio_play_wait_mutex);
+	SDL_CondWait(is->audio_play_cond, is->audio_play_wait_mutex);
+	SDL_UnlockMutex(is->audio_play_wait_mutex);
+
+	is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "----------%s:%d before SDL Close Audio Device.", __FUNCTION__, __LINE__);
+	SDL_CloseAudioDevice(audio_dev);
+	is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "---------%s:%d after SDL Close Audio Device.", __FUNCTION__, __LINE__);
+
+	return 0;
+}
+
+// 音频处理回调函数。读队列获取音频包,解码,播放
+// 此函数被SDL按需调用,此函数不在用户主线程中,因此数据需要保护
+// \param[in]  opaque 用户在注册回调函数时指定的参数
+// \param[out] stream 音频数据缓冲区地址,将解码后的音频数据填入此缓冲区
+// \param[out] len    音频数据缓冲区大小,单位字节
+// 回调函数返回后,stream指向的音频缓冲区将变为无效
+// 双声道采样点的顺序为LRLRLR
+static void sdl_audio_callback(void *opaque, uint8_t*stream, int len)
+{
+    player_stat_t *is = (player_stat_t *)opaque;
+    int audio_size = 0, len1 = 0;
+
+	if (is->buser_stop){
+		SDL_LockMutex(is->audio_play_wait_mutex);
+		SDL_CondSignal(is->audio_play_cond);
+		SDL_UnlockMutex(is->audio_play_wait_mutex);
+		return;
+	}
+
+    int64_t audio_callback_time = av_gettime_relative();
+    while (len > 0 && false == is->buser_stop) // 输入参数len等于is->audio_hw_buf_size,是audio_open()中申请到的SDL音频缓冲区大小
+    {
+        if (is->audio_cp_index >= (int)is->audio_frm_size){
+            // 1. 从音频frame队列中取出一个frame,转换为音频设备支持的格式,返回值是重采样音频帧的大小
+            audio_size = audio_resample(is, audio_callback_time);
+			if (audio_size < 0){
+				if (-1 == audio_size) {
+					if (is->baudio_decode_finished) {
+						is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "audio_size is -1 sdl_audio_callback return, and set abort flag to true.");
+						is->on_audio_play_finished(is->user_data);
+						SDL_LockMutex(is->audio_play_wait_mutex);
+						SDL_CondSignal(is->audio_play_cond);
+						SDL_UnlockMutex(is->audio_play_wait_mutex);
+						return;
+					}
+				}
+                /* if error, just output silence */
+                is->p_audio_frm = NULL;
+                is->audio_frm_size = SDL_AUDIO_MIN_BUFFER_SIZE / is->audio_param_tgt.frame_size * is->audio_param_tgt.frame_size;
+            }
+            else{
+                is->audio_frm_size = audio_size;
+            }
+            is->audio_cp_index = 0;
+        }
+        // 引入is->audio_cp_index的作用:防止一帧音频数据大小超过SDL音频缓冲区大小,这样一帧数据需要经过多次拷贝
+        // 用is->audio_cp_index标识重采样帧中已拷入SDL音频缓冲区的数据位置索引,len1表示本次拷贝的数据量
+        len1 = is->audio_frm_size - is->audio_cp_index;
+        if (len1 > len){
+            len1 = len;
+        }
+        // 2. 将转换后的音频数据拷贝到音频缓冲区stream中,之后的播放就是音频设备驱动程序的工作了
+        if (is->p_audio_frm != NULL){
+			SDL_memset(stream, 0, len1);
+			int ivolume = is->uVolume;
+			if (0 == is->on_audio_volume(&ivolume,is->user_data)){
+				//is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "on_audio_volume success.");
+			}
+			//is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "audio uVolume is %d.", ivolume);
+			//SDL_MixAudio(stream, (uint8_t*)is->p_audio_frm + is->audio_cp_index, len1, ivolume);
+			SDL_MixAudioFormat(stream, (uint8_t*)is->p_audio_frm + is->audio_cp_index, AUDIO_S16SYS, len1, ivolume);
+			if (is->prvc_cb && is->prvc_cb->cb_playing_audiodata) {
+				is->prvc_cb->cb_playing_audiodata(&(is->audio_param_tgt), (uint8_t*)is->p_audio_frm + is->audio_cp_index, len1, is->prvc_cb->user_data);
+			}
+        }
+        else{
+			SDL_memset(stream, 0, len1);
+        }
+
+        len -= len1;
+        stream += len1;
+        is->audio_cp_index += len1;
+    }
+    // is->audio_write_buf_size是本帧中尚未拷入SDL音频缓冲区的数据量
+    is->audio_write_buf_size = is->audio_frm_size - is->audio_cp_index;
+	//is->rvc_hostapi->Debug("audio_write_buf_size == %d.", is->audio_write_buf_size);
+    /* Let's assume the audio driver that is used by SDL has two periods. */
+    // 3. 更新时钟
+    if (!isnan(is->audio_clock))
+    {
+        // 更新音频时钟,更新时刻:每次往声卡缓冲区拷入数据后
+        // 前面audio_decode_frame中更新的is->audio_clock是以音频帧为单位,所以此处第二个参数要减去未拷贝数据量占用的时间
+        set_clock_at(&is->audio_clk,
+            is->audio_clock - (double)(2 * is->audio_hw_buf_size + is->audio_write_buf_size) / is->audio_param_tgt.bytes_per_sec,
+            is->audio_clock_serial,
+            audio_callback_time / 1000000.0);
+    }
+}
+
+int open_audio(player_stat_t *is)
+{
+	is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "enter open_audio()");
+	if (-1 == is->audio_idx[is->icurrent_index]){
+		is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "not find audio stream");
+	}
+	else {
+		open_audio_stream(is);
+		open_audio_playing(is);
+	}
+
+	is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "exit open_audio()");
+
+    return 0;
+}

+ 6 - 0
Other/win/libmediaplayer/audio.h

@@ -0,0 +1,6 @@
+#pragma once
+
+#include "player.h"
+
+int open_audio(player_stat_t *is);
+

+ 201 - 0
Other/win/libmediaplayer/demux.cpp

@@ -0,0 +1,201 @@
+#include "demux.h"
+#include "packet.h"
+
+#ifndef RVC_MAX_BUFFER_LEN
+#define RVC_MAX_BUFFER_LEN 1024
+#endif
+
+static int decode_interrupt_cb(void *ctx)
+{
+    player_stat_t *is = (player_stat_t*)ctx;
+    //return (int)(is->bvideo_finished && is->baudio_finished);
+	return (int)(is->buser_stop);
+}
+
+static int demux_init(player_stat_t *is)
+{
+	int ret = -1;
+	for (int index = 0; index < is->uFilesCount; index++)
+	{
+		AVFormatContext* p_fmt_ctx = NULL;
+		int err = -1;
+		int a_idx = -1;
+		int v_idx = -1;
+
+		p_fmt_ctx = avformat_alloc_context();
+		if (!p_fmt_ctx){
+			is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "Could not allocate context.");
+			ret = AVERROR(ENOMEM);
+			break;
+		}
+		// 中断回调机制。为底层I/O层提供一个处理接口,比如中止IO操作。
+		p_fmt_ctx->interrupt_callback.callback = decode_interrupt_cb;
+		p_fmt_ctx->interrupt_callback.opaque = is;
+		// 1. 构建AVFormatContext
+		// 1.1 打开视频文件:读取文件头,将文件格式信息存储在"fmt context"中
+		err = avformat_open_input(&p_fmt_ctx, is->strPlayLists[index], NULL, NULL);
+		if (err < 0){
+			char buffer[RVC_MAX_BUFFER_LEN] = { 0 };
+			av_strerror(err, buffer, RVC_MAX_BUFFER_LEN);
+			is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "avformat_open_input file %s failed %d(%s).", is->strPlayLists[index], err, buffer);
+			ret = -1;
+			break;
+		}
+		is->p_fmt_ctx[index] = p_fmt_ctx;
+		is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "is->p_fmt_ctx[%d] = 0x%08x, p_fmt_ctx = 0x%08x", index, is->p_fmt_ctx[index], p_fmt_ctx);
+
+		// 1.2 搜索流信息:读取一段视频文件数据,尝试解码,将取到的流信息填入p_fmt_ctx->streams
+		//     ic->streams是一个指针数组,数组大小是pFormatCtx->nb_streams
+		err = avformat_find_stream_info(p_fmt_ctx, NULL);
+		if (err < 0){
+			char strbuffer[RVC_MAX_BUFFER_LEN] = { 0 };
+			av_strerror(err, strbuffer, RVC_MAX_BUFFER_LEN);
+			is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "%s avformat_find_stream_info() failed %d(%s).", is->strPlayLists[index], err, strbuffer);
+			ret = -1;
+			break;
+		}
+
+		// 2. 查找第一个音频流/视频流
+		for (int i = 0; i < (int)p_fmt_ctx->nb_streams; i++){
+			if ((p_fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) && (a_idx == -1)){
+				a_idx = i;
+				is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "%s Find a audio stream, index %d, media type is %s.", is->strPlayLists[index], a_idx, Media_Type_Table[is->eMType]);
+			}
+			if ((p_fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) && (v_idx == -1)){
+				v_idx = i;
+				is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "%s Find a video stream, index %d, media type is %s.", is->strPlayLists[index], v_idx, Media_Type_Table[is->eMType]);
+			}
+			if (a_idx != -1 && v_idx != -1){
+				break;
+			}
+		}
+
+		if ((a_idx == -1 && v_idx == -1) || (eVideo_Type == is->eMType && v_idx == -1)){
+			is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "Invalid file %s can not find any audio/video stream.", is->strPlayLists[index]);
+			ret = -1;
+			if (NULL != p_fmt_ctx){
+				avformat_close_input(&p_fmt_ctx);
+				is->p_fmt_ctx[index] = NULL;
+			}
+			break;
+		}
+
+		is->audio_idx[index] = a_idx;
+		is->video_idx[index] = v_idx;
+		is->p_audio_stream[index] = p_fmt_ctx->streams[a_idx];
+		is->p_video_stream[index] = p_fmt_ctx->streams[v_idx];
+		
+		ret = 0;
+	}
+
+    return ret;
+}
+
+int demux_deinit()
+{
+    return 0;
+}
+
+static int stream_has_enough_packets(AVStream *st, int stream_id, packet_queue_t *queue)
+{
+    return stream_id < 0 ||
+           queue->abort_flag ||
+           (st->disposition & AV_DISPOSITION_ATTACHED_PIC) ||
+           queue->nb_packets > MIN_FRAMES && (!queue->duration || av_q2d(st->time_base) * queue->duration > 1.0);
+}
+
+/* this thread gets the stream from the disk or the network */
+static int demux_thread(void *arg)
+{
+    player_stat_t *is = (player_stat_t *)arg;
+    //AVFormatContext *p_fmt_ctx = is->p_fmt_ctx;
+    int ret = 0;
+    AVPacket pkt1, *pkt = &pkt1;
+
+    SDL_mutex *wait_mutex = SDL_CreateMutex();
+
+	is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "begin %s av_read_frame, nFilecount is %d. current index is %d.", is->strPlayLists[is->icurrent_index], is->uFilesCount, is->icurrent_index);
+    // 4. 解复用处理
+	while (false == is->buser_stop)
+    {
+        /* if the queue are full, no need to read more */
+        if (is->audio_pkt_queue.packet_queue_size + is->video_pkt_queue.packet_queue_size > MAX_QUEUE_SIZE ||
+            (stream_has_enough_packets(is->p_audio_stream[is->icurrent_index], is->audio_idx[is->icurrent_index], &is->audio_pkt_queue) &&
+             stream_has_enough_packets(is->p_video_stream[is->icurrent_index], is->video_idx[is->icurrent_index], &is->video_pkt_queue)))
+        {
+            /* wait 10 ms */
+            SDL_LockMutex(wait_mutex);
+            SDL_CondWaitTimeout(is->continue_read_thread, wait_mutex, 10);
+            SDL_UnlockMutex(wait_mutex);
+            continue;
+        }
+
+        // 4.1 从输入文件中读取一个packet
+        ret = av_read_frame(is->p_fmt_ctx[is->icurrent_index], pkt);
+        if (ret < 0)
+        {
+            if (AVERROR_EOF == ret)
+            {
+                // 输入文件已读完,则往packet队列中发送NULL packet,以冲洗(flush)解码器,否则解码器中缓存的帧取不出来
+                if (is->video_idx[is->icurrent_index] >= 0){
+                    packet_queue_put_nullpacket(&is->video_pkt_queue, is->video_idx[is->icurrent_index], is->rvc_hostapi);
+                }
+
+                if (is->audio_idx[is->icurrent_index] >= 0){
+                    packet_queue_put_nullpacket(&is->audio_pkt_queue, is->audio_idx[is->icurrent_index], is->rvc_hostapi);
+                }
+
+				if (is->icurrent_index + 1 < is->uFilesCount){
+					is->icurrent_index++;
+					ret = 0;
+					is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "begin %s av_read_frame, nFilecount is %d, current is->index is %d.", is->strPlayLists[is->icurrent_index], is->uFilesCount, is->icurrent_index);
+				}
+				else {
+					is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "av_read_frame ret is AVERROR_EOF.");
+					break;
+				}
+            }
+
+            SDL_LockMutex(wait_mutex);
+            SDL_CondWaitTimeout(is->continue_read_thread, wait_mutex, 10);
+            SDL_UnlockMutex(wait_mutex);
+            continue;
+        }
+        
+        // 4.3 根据当前packet类型(音频、视频、字幕),将其存入对应的packet队列
+        if (pkt->stream_index == is->audio_idx[is->icurrent_index]){
+            packet_queue_put(&is->audio_pkt_queue, pkt, is->rvc_hostapi);
+        }
+        else if (pkt->stream_index == is->video_idx[is->icurrent_index]){
+            packet_queue_put(&is->video_pkt_queue, pkt, is->rvc_hostapi);
+        }
+        else{
+            av_packet_unref(pkt);
+        }
+    }
+
+    SDL_DestroyMutex(wait_mutex);
+	is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "%s exit, thread id is %u, buser_stop flag is %s.",SDL_GetThreadName(is->read_tid), SDL_ThreadID(), is->buser_stop ? "true" : "false");
+
+    return 0;
+}
+
+
+int open_demux(player_stat_t *is)
+{
+    if (demux_init(is) != 0){
+		is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "demux_init() failed.");
+        return -1;
+    }
+
+    is->read_tid = SDL_CreateThread(demux_thread, "demux_thread", is);
+    if (is->read_tid == NULL){
+		is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "SDL_CreateThread() failed: %s.", SDL_GetError());
+        return -1;
+    }
+	else {
+		is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "create %s success, and thread id is %u.", SDL_GetThreadName(is->read_tid), SDL_GetThreadID(is->read_tid));
+	}
+
+    return 0;
+}

+ 6 - 0
Other/win/libmediaplayer/demux.h

@@ -0,0 +1,6 @@
+#pragma once
+
+#include "player.h"
+
+int open_demux(player_stat_t *is);
+

+ 19 - 0
Other/win/libmediaplayer/dllmain.cpp

@@ -0,0 +1,19 @@
+// dllmain.cpp : Defines the entry point for the DLL application.
+#include "stdafx.h"
+
+BOOL APIENTRY DllMain( HMODULE hModule,
+                       DWORD  ul_reason_for_call,
+                       LPVOID lpReserved
+					 )
+{
+	switch (ul_reason_for_call)
+	{
+	case DLL_PROCESS_ATTACH:
+	case DLL_THREAD_ATTACH:
+	case DLL_THREAD_DETACH:
+	case DLL_PROCESS_DETACH:
+		break;
+	}
+	return TRUE;
+}
+

+ 151 - 0
Other/win/libmediaplayer/frame.cpp

@@ -0,0 +1,151 @@
+#include "frame.h"
+#include "player.h"
+
+void frame_queue_unref_item(frame_t *vp)
+{
+    av_frame_unref(vp->frame);
+}
+
+int frame_queue_init(frame_queue_t *f, packet_queue_t *pktq, int max_size, int keep_last)
+{
+    memset(f, 0, sizeof(frame_queue_t));
+    
+	if (!(f->frame_mutex = SDL_CreateMutex())) {
+        return AVERROR(ENOMEM);
+    }
+
+    if (!(f->frame_cond = SDL_CreateCond())) {
+        return AVERROR(ENOMEM);
+    }
+
+    f->pktq = pktq;
+    f->max_size = FFMIN(max_size, FRAME_QUEUE_SIZE);
+    f->keep_last = !!keep_last;
+
+	for (int i = 0; i < f->max_size; i++) {
+		if (!(f->queue[i].frame = av_frame_alloc())) {
+			return AVERROR(ENOMEM);
+		}
+	}
+
+    return 0;
+}
+
+void frame_queue_destory(frame_queue_t *f)
+{
+    for (int i = 0; i < f->max_size; i++) {
+        frame_t *vp = &f->queue[i];
+        frame_queue_unref_item(vp);
+        av_frame_free(&vp->frame);
+    }
+
+    SDL_DestroyMutex(f->frame_mutex);
+    SDL_DestroyCond(f->frame_cond);
+}
+
+void frame_queue_signal(frame_queue_t *f)
+{
+    SDL_LockMutex(f->frame_mutex);
+    SDL_CondSignal(f->frame_cond);
+    SDL_UnlockMutex(f->frame_mutex);
+}
+
+frame_t *frame_queue_peek(frame_queue_t *f)
+{
+    return &f->queue[(f->rindex + f->rindex_shown) % f->max_size];
+}
+
+frame_t *frame_queue_peek_next(frame_queue_t *f)
+{
+    return &f->queue[(f->rindex + f->rindex_shown + 1) % f->max_size];
+}
+
+// 取出此帧进行播放,只读取不删除,不删除是因为此帧需要缓存下来供下一次使用。播放后,此帧变为上一帧
+frame_t *frame_queue_peek_last(frame_queue_t *f)
+{
+    return &f->queue[f->rindex];
+}
+
+// 向队列尾部申请一个可写的帧空间,若无空间可写,则等待
+frame_t *frame_queue_peek_writable(frame_queue_t *f)
+{
+    /* wait until we have space to put a new frame */
+    SDL_LockMutex(f->frame_mutex);
+    while (f->size >= f->max_size && !f->pktq->abort_flag) {
+		SDL_CondWait(f->frame_cond, f->frame_mutex);
+    }
+    SDL_UnlockMutex(f->frame_mutex);
+
+	if (f->pktq->abort_flag) {
+		return NULL;
+	}
+
+    return &f->queue[f->windex];
+}
+
+// 从队列头部读取一帧,只读取不删除,若无帧可读则等待
+frame_t *frame_queue_peek_readable(frame_queue_t *f)
+{
+    /* wait until we have a readable a new frame */
+	bool bexit = false;
+    SDL_LockMutex(f->frame_mutex);
+    while (f->size - f->rindex_shown <= 0 && !f->pktq->abort_flag) {
+		SDL_CondWait(f->frame_cond, f->frame_mutex);
+    }
+    SDL_UnlockMutex(f->frame_mutex);
+
+	if (f->pktq->abort_flag || bexit) {
+		return NULL;
+	}
+
+    return &f->queue[(f->rindex + f->rindex_shown) % f->max_size];
+}
+
+// 向队列尾部压入一帧,只更新计数与写指针,因此调用此函数前应将帧数据写入队列相应位置
+void frame_queue_push(frame_queue_t *f)
+{
+	if (++f->windex == f->max_size) {
+		f->windex = 0;
+	}
+    SDL_LockMutex(f->frame_mutex);
+    f->size++;
+    SDL_CondSignal(f->frame_cond);
+    SDL_UnlockMutex(f->frame_mutex);
+}
+
+// 读指针(rindex)指向的帧已显示,删除此帧,注意不读取直接删除。读指针加1
+void frame_queue_next(frame_queue_t *f)
+{
+    if (f->keep_last && !f->rindex_shown) {
+        f->rindex_shown = 1;
+        return;
+    }
+    frame_queue_unref_item(&f->queue[f->rindex]);
+	if (++f->rindex == f->max_size) {
+		f->rindex = 0;
+	}
+    SDL_LockMutex(f->frame_mutex);
+    f->size--;
+	SDL_CondSignal(f->frame_cond);
+    SDL_UnlockMutex(f->frame_mutex);
+}
+
+// frame_queue中未显示的帧数
+/* return the number of undisplayed frames in the queue */
+int frame_queue_nb_remaining(frame_queue_t *f)
+{
+    return f->size - f->rindex_shown;
+}
+
+/* return last shown position */
+int64_t frame_queue_last_pos(frame_queue_t *f)
+{
+    frame_t *fp = &f->queue[f->rindex];
+	if (f->rindex_shown && fp->serial == f->pktq->serial) {
+		return fp->pos;
+	}
+	else {
+		return -1;
+	}
+}
+

+ 18 - 0
Other/win/libmediaplayer/frame.h

@@ -0,0 +1,18 @@
+#pragma once
+
+#include "player.h"
+
+void frame_queue_unref_item(frame_t *vp);
+int frame_queue_init(frame_queue_t *f, packet_queue_t *pktq, int max_size, int keep_last);
+void frame_queue_destory(frame_queue_t *f);
+void frame_queue_signal(frame_queue_t *f);
+frame_t *frame_queue_peek(frame_queue_t *f);
+frame_t *frame_queue_peek_next(frame_queue_t *f);
+frame_t *frame_queue_peek_last(frame_queue_t *f);
+frame_t *frame_queue_peek_writable(frame_queue_t *f);
+frame_t *frame_queue_peek_readable(frame_queue_t *f);
+void frame_queue_push(frame_queue_t *f);
+void frame_queue_next(frame_queue_t *f);
+int frame_queue_nb_remaining(frame_queue_t *f);
+int64_t frame_queue_last_pos(frame_queue_t *f);
+

+ 85 - 0
Other/win/libmediaplayer/idatastruct.h

@@ -0,0 +1,85 @@
+#pragma once
+
+#ifdef _WIN32
+#ifndef RVC_NO_VTABLE
+#define RVC_NO_VTABLE __declspec(novtable)
+#endif // !RVC_NO_VTABLE
+
+#else
+#ifndef RVC_NO_VTABLE
+#define RVC_NO_VTABLE 
+#endif // !RVC_NO_VTABLE
+#endif // RVC_OS_WIN
+
+
+#ifndef TIME_LEN
+#define TIME_LEN      9
+#endif // !TIME_LEN
+
+#ifndef MAX_FILECOUNT
+#define MAX_FILECOUNT 32
+#endif // !MAX_FILECOUNT
+
+#ifndef MAX_PATH
+#define MAX_PATH 260
+#endif // !MAX_PATH
+
+typedef struct audio_param_s {
+	int freq;
+	int channels;
+	int channel_layout;
+	int fmt;
+	int frame_size;
+	int bytes_per_sec;
+}audio_param_t;
+
+
+enum media_loglevel {
+	MEDIA_LOG_NO,
+	MEDIA_LOG_DEBUG,
+	MEDIA_LOG_INFO,
+	MEDIA_LOG_ERROR
+};
+
+enum MediaPlayModeEnum
+{
+	MEDIA_UNKNOWN = -1,                               
+	MEDIA_SINGLE,                                     
+	MEDIA_SALESRECORD,                                
+	MEDIA_LOCALAUDIO,                                
+	MEDIA_LOCALVIDEO,                                 
+	MEDIA_THRIDSALESRECORD							  
+};
+
+struct CMediaPlayConfig
+{
+	char strVideoRunTime_S[TIME_LEN];				
+	char strVideoRunTime_E[TIME_LEN];				
+	bool bFullScreen;                           
+	bool bPrimMonitor;                          
+	bool bSimpleMode;                           
+	MediaPlayModeEnum eMode;                   
+	int nWndX;                                 
+	int nWndY;                                  
+	int nWndWidth;                             
+	int nWndHeight;                            
+	int nFileCnt;		                       
+	int nPlayCnt;		                       
+	int nPlayInterval;		                    
+	char strNamePrefix[MAX_PATH];              
+	char strRootPath[MAX_PATH];		           
+	char strFileNames[MAX_FILECOUNT][MAX_PATH]; 
+
+	int nVolume;				
+};
+
+
+struct RVC_NO_VTABLE CMediaHostApi
+{
+	virtual int LoadPlayConfig(CMediaPlayConfig& config, int CfgInx = 0) = 0;
+	virtual void Debug(media_loglevel log_level, const char* fmt, ...) = 0;
+	virtual void MediaPlayFinished(int iMediaType) = 0;
+	virtual int GetMediaPlayerIcoPath(char* strPath, size_t uLen) = 0;
+	virtual int GetAudioOutDevName(char* strDev, size_t uLen) = 0;
+	virtual int PlayingAudioDataCallback(audio_param_t *param, const void* input, unsigned long uaudiolen) = 0;
+};

+ 586 - 0
Other/win/libmediaplayer/libmediaplayer.cpp

@@ -0,0 +1,586 @@
+#include "libmediaplayer.h"
+#include "player.h"
+#include <stdlib.h>
+#include <string.h>
+#include <io.h>
+
+class libmediaplayer_impl
+{
+private:
+	CMediaHostApi* m_pHostApi;
+	CMediaPlayer* m_Player;
+	CMediaPlayConfig m_stPlayConfig;
+	
+public:
+	bool m_bisplaying;
+
+public:
+	libmediaplayer_impl(CMediaHostApi* pHostApi);
+	~libmediaplayer_impl();
+
+	bool isStop();
+	void PlayMediaFinished();
+	int StartPlayVideo(const char* pVideoDir, const char* pNamePrefix = NULL, int nVideoCount = 1);
+	int GetLocalAudioPlayingParams(rvc_media_player_param_t* pParam, const char* pAudioNames);
+	int StartPlayLocalAudio(const char* pAudioNames);
+	int GetLocalVideoPlayingParams(rvc_media_player_param_t* pParam);
+	int StartPlayLocalVideo(int nCfgInx, int nWndX, int nWndY, int nWndWidth, int nWndHeight);
+	int StartPlayMedia(CMediaPlayConfig& config);
+	bool StopPlay();
+	void SetVolume(int nVolume);
+	void StartPlaySalesRecordVideo(int nWndX, int nWndY, int nWndWidth, int nWndHeight, const char* pVideoDir, const char* pNamePrefix = NULL, int nVideoCount = 1);
+	void StartPlayVideoNotice(int nWndX, int nWndY, int nWndWidth, int nWndHeight, const char* pFileName);
+	bool checkIsPlay();
+	bool checkIsStop();
+	CMediaHostApi* GetHostApi();
+	int PlayingAudioPcmCallback(audio_param_t* param, const void* input, unsigned long uaudiolen);
+	bool IsFileValid(const char* pVideoName);
+};
+
+
+bool IsFileExist(const char* pFilePath)
+{
+	bool bRet = false;
+	if (NULL != pFilePath) {
+		if (0 == access(pFilePath, 0)) {
+			bRet = true;
+		}
+	}
+	return bRet;
+}
+
+
+void CStringSplit(char* str, char** result, const char* del)
+{
+	char* ptr = NULL;
+	char* p = strtok_s(str, del, &ptr);
+	while (p != NULL)
+	{
+		*result++ = p;
+		p = strtok_s(NULL, del, &ptr);
+	}
+}
+
+
+static void __cb_play_finished(void* user_data)
+{
+	libmediaplayer_impl* pthis = static_cast<libmediaplayer_impl*>(user_data);
+	if (NULL != pthis) {
+		pthis->PlayMediaFinished();
+		pthis->m_bisplaying = false;
+	}
+}
+
+
+static int __cb_playing_audio_data(audio_param_t* param, const void* input, unsigned long uaudiolen, void* user_data)
+{
+	int iret = -1;
+	libmediaplayer_impl* pthis = static_cast<libmediaplayer_impl*>(user_data);
+	if (NULL != pthis) {
+		iret = pthis->PlayingAudioPcmCallback(param, input, uaudiolen);
+	}
+
+	return iret;
+}
+
+
+libmediaplayer_impl::libmediaplayer_impl(CMediaHostApi* pHostApi)
+{
+	m_pHostApi = pHostApi;
+
+	m_Player = new CMediaPlayer(pHostApi);
+	if (NULL == m_Player) {
+		pHostApi->Debug(MEDIA_LOG_DEBUG, "new MediaPlayer failed!");
+	}
+	memset(&m_stPlayConfig, 0, sizeof(CMediaPlayConfig));
+	m_bisplaying = false;
+}
+
+
+libmediaplayer_impl::~libmediaplayer_impl()
+{
+	m_pHostApi = NULL;
+	delete m_Player;
+	m_Player = NULL;
+	m_bisplaying = false;
+}
+
+
+bool libmediaplayer_impl::isStop() 
+{
+	return !m_bisplaying; 
+}
+
+
+void libmediaplayer_impl::PlayMediaFinished()
+{
+	m_pHostApi->Debug(MEDIA_LOG_DEBUG, "PlayMediaFinished!");
+	m_pHostApi->MediaPlayFinished(m_Player->GetPlayingMediaType());
+}
+
+
+int libmediaplayer_impl::PlayingAudioPcmCallback(audio_param_t* param, const void* input, unsigned long uaudiolen)
+{
+	return m_pHostApi->PlayingAudioDataCallback(param, input, uaudiolen);
+}
+
+
+bool libmediaplayer_impl::IsFileValid(const char* pVideoName)
+{
+	bool bret= false;
+	int err = -1;
+
+	AVFormatContext* p_fmt_ctx = avformat_alloc_context();
+	if (!p_fmt_ctx){
+		return bret;
+	}
+	
+	// 1. 构建AVFormatContext
+	// 1.1 打开视频文件:读取文件头,将文件格式信息存储在"fmt context"中
+	err = avformat_open_input(&p_fmt_ctx, pVideoName, NULL, NULL);
+	if (0 == err){
+		err = avformat_find_stream_info(p_fmt_ctx, NULL);
+		if (err >= 0){
+			for (int i = 0; i < (int)p_fmt_ctx->nb_streams; i++){
+				if ((p_fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) || AVMEDIA_TYPE_VIDEO == p_fmt_ctx->streams[i]->codecpar->codec_type){
+					bret = true;
+				}
+			}
+		}
+	}
+
+	avformat_close_input(&p_fmt_ctx);
+	avformat_free_context(p_fmt_ctx);
+
+	return bret;
+}
+
+
+int libmediaplayer_impl::StartPlayVideo(const char* pVideoDir, const char* pNamePrefix, int nVideoCount)
+{
+	int iRet = -1;
+	if (NULL == pVideoDir || NULL == pNamePrefix) {
+		return iRet;
+	}
+
+	play_media_callback_t cb = {0};
+	cb.cb_play_media_finished = &__cb_play_finished;
+	cb.user_data = this;
+
+	m_pHostApi->Debug(MEDIA_LOG_DEBUG, "audio root path is %s.", pVideoDir);
+	char strVideoName[MAX_PATH] = { 0 };
+	snprintf(strVideoName, MAX_PATH, "%s/%s", pVideoDir, pNamePrefix);
+	m_pHostApi->Debug(MEDIA_LOG_DEBUG, "video full path is %s.", strVideoName);
+	if (!IsFileExist(strVideoName)) {
+		return iRet;
+	}
+
+	rvc_media_player_param_t t_param = { 0 };
+	t_param.p_input_file = strVideoName;
+	t_param.cb = &cb;
+	t_param.eType = eVideo_Type;
+	t_param.eWindType = eVideoSize_Type;
+	t_param.idisplaycx = SDL_WINDOWPOS_UNDEFINED;
+	t_param.idisplaycy = SDL_WINDOWPOS_UNDEFINED;
+
+	memcpy(t_param.strPlayLists[0], strVideoName, strlen(strVideoName));
+	t_param.uFilesCount = 1;
+
+	if (0 == m_Player->InitParam(&t_param)) {
+		m_bisplaying = true;
+		iRet = m_Player->StartMediaPlay();
+	}
+
+	return iRet;
+}
+
+
+int libmediaplayer_impl::GetLocalAudioPlayingParams(rvc_media_player_param_t* pParam, const char* pAudioNames)
+{
+	int iRet = -1;
+	if (NULL == pParam || NULL == pAudioNames) {
+		return iRet;
+	}
+
+	pParam->eType = eAudio_Type;
+	pParam->eWindType = eVideoSize_Type;
+
+	size_t uLen = strlen(pAudioNames);
+	char* Tmp = new char[uLen + 1];
+	memset(Tmp, 0, uLen + 1);
+	memcpy(Tmp, pAudioNames, uLen);
+
+	char* Result[MAX_FILECOUNT] = { NULL };
+	CStringSplit(Tmp, Result, "|");
+	int FileCount = 0;
+	char** pStr = Result;
+	while (*pStr != NULL) {
+		++pStr;
+		++FileCount;
+	}
+	m_stPlayConfig.bPrimMonitor = true;
+	m_stPlayConfig.nFileCnt = FileCount;
+	m_pHostApi->Debug(MEDIA_LOG_DEBUG, "wmp pAudioNames = %s!", pAudioNames);
+	m_pHostApi->Debug(MEDIA_LOG_DEBUG, "wmp config.nFileCnt = %d!", FileCount);
+	size_t uValidCount = 0;
+	for (int i = 0; i < FileCount && i < MAX_FILECOUNT; i++) {
+		char strFileName[MAX_PATH] = { 0 };
+		snprintf(strFileName, MAX_PATH, "%s%s", m_stPlayConfig.strRootPath, Result[i]);
+		if (IsFileExist(strFileName)) {
+			memcpy(pParam->strPlayLists[i], strFileName, strlen(strFileName));
+			uValidCount++;
+		}
+		else {
+			m_pHostApi->Debug(MEDIA_LOG_DEBUG, "File %s is not exist.", strFileName);
+			continue;
+		}
+	}
+	delete[] Tmp;
+	Tmp = NULL;
+
+	pParam->uFilesCount = uValidCount;
+	m_pHostApi->Debug(MEDIA_LOG_DEBUG, "pParam uFilesCount = %d", pParam->uFilesCount);
+	if (uValidCount > 0) {
+		iRet = 0;
+	}
+
+	return iRet;
+}
+
+
+int libmediaplayer_impl::StartPlayLocalAudio(const char* pAudioNames)
+{
+	int iRet = -1;
+	m_pHostApi->Debug(MEDIA_LOG_DEBUG, "StartPlayLocalAudio %s.", pAudioNames);
+
+	m_stPlayConfig.eMode = MEDIA_LOCALAUDIO;
+	iRet = m_pHostApi->LoadPlayConfig(m_stPlayConfig);
+	m_stPlayConfig.eMode = MEDIA_LOCALAUDIO;
+	if (0 != iRet) {
+		m_pHostApi->Debug(MEDIA_LOG_ERROR, "Load WmpConfiguration failed while play local audio!");
+		return iRet;
+	}
+	else {
+		m_pHostApi->Debug(MEDIA_LOG_DEBUG, "Load WmpConfiguration succeeded while play local audio!");
+		m_pHostApi->Debug(MEDIA_LOG_DEBUG, "m_stPlayConfig.strRootPath: %s", m_stPlayConfig.strRootPath);
+	}
+
+	rvc_media_player_param_t t_param = { 0 };
+	if (0 != GetLocalAudioPlayingParams(&t_param, pAudioNames)) {
+		return iRet;
+	}
+
+	play_media_callback_t cb = { 0 };
+	cb.cb_play_media_finished = &__cb_play_finished;
+	cb.cb_playing_audiodata = &__cb_playing_audio_data;
+	cb.user_data = this;
+	t_param.cb = &cb;
+
+	if (0 == m_Player->InitParam(&t_param)) {
+		m_pHostApi->Debug(MEDIA_LOG_DEBUG, "Player Init success!");
+		m_bisplaying = true;
+		iRet = m_Player->StartMediaPlay();
+	}
+	else {
+		m_pHostApi->Debug(MEDIA_LOG_ERROR, "Player Init failed!");
+	}
+	return iRet;
+}
+
+
+int libmediaplayer_impl::GetLocalVideoPlayingParams(rvc_media_player_param_t* pParam)
+{
+	int iRet = -1;
+	if (NULL == pParam) {
+		return iRet;
+	}
+
+	pParam->eType = eVideo_Type;
+	pParam->eWindType = eFullScreen_Type;
+	size_t uValidCount = 0;
+	for (int i = 0; i < m_stPlayConfig.nFileCnt && i < MAX_FILECOUNT; i++) {
+		char strFileName[MAX_PATH] = { 0 };
+		snprintf(strFileName, MAX_PATH, "%s%s", m_stPlayConfig.strRootPath, m_stPlayConfig.strFileNames[i]);
+		if (IsFileExist(strFileName)) {
+			memcpy(pParam->strPlayLists[i], strFileName, strlen(strFileName));
+			uValidCount++;
+		}
+		else {
+			m_pHostApi->Debug(MEDIA_LOG_DEBUG, "File %s is not exist.", strFileName);
+			continue;
+		}
+	}
+	pParam->uFilesCount = uValidCount;
+	m_pHostApi->Debug(MEDIA_LOG_DEBUG, "pParam uFilesCount = %d", pParam->uFilesCount);
+	if (uValidCount > 0) {
+		iRet = 0;
+	}
+
+	return iRet;
+}
+
+
+int libmediaplayer_impl::StartPlayLocalVideo(int nCfgInx, int nWndX, int nWndY, int nWndWidth, int nWndHeight)
+{
+	int iRet = -1;
+	m_stPlayConfig.eMode = MEDIA_LOCALVIDEO;
+	m_pHostApi->Debug(MEDIA_LOG_DEBUG, "nCfgInx=%d, nWndX=%d, nWndY=%d, nWndWidth=%d, nWndHeight=%d!", nCfgInx, nWndX, nWndY, nWndWidth, nWndHeight);
+	iRet = m_pHostApi->LoadPlayConfig(m_stPlayConfig, nCfgInx);
+	m_stPlayConfig.eMode = MEDIA_LOCALVIDEO;
+	m_stPlayConfig.nWndX = nWndX;
+	m_stPlayConfig.nWndY = nWndY;
+	m_stPlayConfig.nWndWidth = nWndWidth;
+	m_stPlayConfig.nWndHeight = nWndHeight;
+	if (0 != iRet) {
+		m_pHostApi->Debug(MEDIA_LOG_DEBUG, "Load WmpConfiguration failed while play local video!");
+		return iRet;
+	}
+	else {
+		m_pHostApi->Debug(MEDIA_LOG_DEBUG, "Load WmpConfiguration succeeded while play local video!");
+		m_pHostApi->Debug(MEDIA_LOG_DEBUG, "m_stPlayConfig.strRootPath: %s,m_stPlayConfig.nFileCnt:%d", m_stPlayConfig.strRootPath, m_stPlayConfig.nFileCnt);
+	}
+
+	struct tm* ptm = NULL;
+	time_t t = time(NULL);
+	ptm = localtime(&t);
+	char strNow[TIME_LEN] = { 0 };
+	sprintf(strNow, "%02d:%02d:%02d", ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
+	if (strcmp(strNow, m_stPlayConfig.strVideoRunTime_S) < 0 || strcmp(strNow, m_stPlayConfig.strVideoRunTime_E) >= 0)
+	{
+		//m_pHostApi->Debug("Now is %s, video play start time is %s, video play stop time is %s, play video rejected!", strNow, m_stPlayConfig.strVideoRunTime_S, m_stPlayConfig.strVideoRunTime_E);
+		iRet = 0;
+		return iRet;
+	}
+
+	rvc_media_player_param_t t_param = { 0 };
+	if (0 != GetLocalVideoPlayingParams(&t_param)) {
+		return iRet;
+	}
+
+	t_param.idisplaycx = SDL_WINDOWPOS_UNDEFINED;
+	t_param.idisplaycy = SDL_WINDOWPOS_UNDEFINED;
+	t_param.bvicemonitor = true;
+
+	play_media_callback_t cb = { 0 };
+	cb.cb_play_media_finished = &__cb_play_finished;
+	cb.user_data = this;
+	t_param.cb = &cb;
+
+	if (0 == m_Player->InitParam(&t_param)) {
+		m_bisplaying = true;
+		iRet = m_Player->StartMediaPlay();
+	}
+	else {
+		m_pHostApi->Debug(MEDIA_LOG_DEBUG, "player init failed!");
+	}
+
+	return iRet;
+}
+
+
+int libmediaplayer_impl::StartPlayMedia(CMediaPlayConfig& config)
+{
+	int iRet = -1;
+	memcpy(&m_stPlayConfig, &config, sizeof(CMediaPlayConfig));
+
+	struct tm* ptm = NULL;
+	time_t t = time(NULL);
+	ptm = localtime(&t);
+	char strNow[TIME_LEN] = { 0 };
+	snprintf(strNow, TIME_LEN, "%02d:%02d:%02d", ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
+	if (strcmp(strNow, m_stPlayConfig.strVideoRunTime_S) < 0 || strcmp(strNow, m_stPlayConfig.strVideoRunTime_E) >= 0)
+	{
+		m_pHostApi->Debug(MEDIA_LOG_DEBUG, "Now is %s, video play start time is %s, video play stop time is %s, play video rejected!", strNow, m_stPlayConfig.strVideoRunTime_S, m_stPlayConfig.strVideoRunTime_E);
+		iRet = -2;
+		return iRet;
+	}
+
+	rvc_media_player_param_t t_param = { 0 };
+	//if (0 != GetLocalVideoPlayingParams(&t_param)) {
+	//	return iRet;
+	//}
+	t_param.eType = eVideo_Type;
+	t_param.eWindType = eFullScreen_Type;
+	t_param.bvicemonitor = true;
+
+	char strFileName[MAX_PATH] = { 0 };
+	snprintf(strFileName, MAX_PATH, "%s%s", m_stPlayConfig.strRootPath, m_stPlayConfig.strFileNames[0]);
+	if (IsFileExist(strFileName)) {
+		memcpy(t_param.strPlayLists[0], strFileName, strlen(strFileName));
+		t_param.uFilesCount = 1;
+	}
+	else {
+		m_pHostApi->Debug(MEDIA_LOG_DEBUG, "File %s is not exist.", strFileName);
+		t_param.uFilesCount = 0;
+		return iRet;
+	}
+
+	m_pHostApi->Debug(MEDIA_LOG_DEBUG, "pParam uFilesCount = %d", t_param.uFilesCount);
+
+	play_media_callback_t cb = { 0 };
+	cb.cb_play_media_finished = &__cb_play_finished;
+	cb.user_data = this;
+	t_param.cb = &cb;
+
+	m_Player->SetVolume(config.nVolume);
+	if (0 == m_Player->InitParam(&t_param)) {
+		m_bisplaying = true;
+		iRet = m_Player->StartMediaPlay();
+	}
+	else {
+		m_pHostApi->Debug(MEDIA_LOG_DEBUG, "Player Init failed!");
+	}
+	return iRet;
+}
+
+bool libmediaplayer_impl::StopPlay()
+{
+	if (m_Player->GetPlayingFlag())
+	{
+		m_Player->StopMediaPlay();
+	}
+
+	return true;
+}
+
+void libmediaplayer_impl::SetVolume(int nVolume)
+{
+	//if (m_Player->GetPlayingFlag())
+	{
+		m_Player->SetVolume(nVolume);
+	}
+}
+
+void libmediaplayer_impl::StartPlaySalesRecordVideo(int nWndX, int nWndY, int nWndWidth, int nWndHeight, const char* pVideoDir, const char* pNamePrefix, int nVideoCount)
+{
+
+}
+
+void libmediaplayer_impl::StartPlayVideoNotice(int nWndX, int nWndY, int nWndWidth, int nWndHeight, const char* pFileName)
+{
+	int iRet = -1;
+	if (NULL == pFileName) {
+		return;
+	}
+
+	rvc_media_player_param_t t_param = { 0 };
+	t_param.uFilesCount = 1;
+	t_param.eType = eVideo_Type;
+	t_param.eWindType = eSpecified_Type;
+	t_param.idisplaycx = nWndX;
+	t_param.idisplaycy = nWndY;
+	t_param.idisplaywidth = nWndWidth;
+	t_param.idisplayheight = nWndHeight;
+	t_param.bvicemonitor = false;
+	memcpy(&t_param.strPlayLists[0], pFileName, strlen(pFileName));
+
+	play_media_callback_t cb = { 0 };
+	cb.cb_play_media_finished = &__cb_play_finished;
+	cb.user_data = this;
+	t_param.cb = &cb;
+
+	if (0 == m_Player->InitParam(&t_param)) {
+		m_bisplaying = true;
+		iRet = m_Player->StartMediaPlay();
+	}
+	else {
+		m_pHostApi->Debug(MEDIA_LOG_DEBUG, "player init failed!");
+	}
+
+	return;
+}
+
+
+bool libmediaplayer_impl::checkIsPlay()
+{
+	return m_bisplaying;
+}
+
+bool libmediaplayer_impl::checkIsStop()
+{
+	return !m_bisplaying;
+}
+
+CMediaHostApi* libmediaplayer_impl::GetHostApi() 
+{ 
+	return m_pHostApi; 
+}
+
+Clibmediaplayer::Clibmediaplayer(CMediaHostApi* pHostApi)
+{
+	m_pImpl = new libmediaplayer_impl(pHostApi);
+	return;
+}
+
+Clibmediaplayer::~Clibmediaplayer()
+{
+	delete m_pImpl;
+	m_pImpl = NULL;
+}
+
+int Clibmediaplayer::PlayVideo(const char* pVideoDir, const char* pNamePrefix, int nVideoCount)
+{
+	return m_pImpl->StartPlayVideo(pVideoDir, pNamePrefix, nVideoCount);
+}
+
+int Clibmediaplayer::PlayLocalAudio(const char* pAudioNames)
+{
+	m_pImpl->GetHostApi()->Debug(MEDIA_LOG_DEBUG, "Clibmediaplayer StartPlayLocalAudio");
+	return m_pImpl->StartPlayLocalAudio(pAudioNames);
+}
+
+int Clibmediaplayer::PlayLocalVideo(int nCfgInx, int nWndX, int nWndY, int nWndWidth, int nWndHeight)
+{
+	return m_pImpl->StartPlayLocalVideo(nCfgInx, nWndX, nWndY, nWndWidth, nWndHeight);
+}
+
+bool Clibmediaplayer::checkIsPlay()
+{
+	return m_pImpl->checkIsPlay();
+}
+
+bool Clibmediaplayer::checkIsStop()
+{
+	return m_pImpl->isStop();
+}
+
+int Clibmediaplayer::PlayMedia(CMediaPlayConfig& config)
+{
+	return m_pImpl->StartPlayMedia(config);
+}
+
+void Clibmediaplayer::Close()
+{
+	m_pImpl->StopPlay();
+}
+
+void Clibmediaplayer::SetVolume(int nVolume)
+{
+	m_pImpl->SetVolume(nVolume);
+}
+
+void Clibmediaplayer::PlaySalesRecordVideo(int nWndX, int nWndY, int nWndWidth, int nWndHeight, const char* pVideoDir, const char* pNamePrefix, int nVideoCount)
+{
+	m_pImpl->StartPlaySalesRecordVideo(nWndX, nWndY, nWndWidth, nWndHeight, pVideoDir, pNamePrefix, nVideoCount);
+}
+
+void Clibmediaplayer::PlayVideoNotice(int nWndX, int nWndY, int nWndWidth, int nWndHeight, const char* pFileName)
+{
+	m_pImpl->StartPlayVideoNotice(nWndX, nWndY, nWndWidth, nWndHeight, pFileName);
+}
+
+
+bool Clibmediaplayer::IsFileValid(const char* pVideoName)
+{
+	return m_pImpl->IsFileValid(pVideoName);
+}
+
+int mediaplayer_init()
+{
+	return 0;
+}
+
+int mediaplayer_term()
+{
+	return 0;
+}

+ 54 - 0
Other/win/libmediaplayer/libmediaplayer.h

@@ -0,0 +1,54 @@
+#pragma once
+#include <stdio.h>
+
+#include "idatastruct.h"
+
+#ifdef _WIN32
+#ifdef LIBMEDIAPLAYER_EXPORTS
+#define LIBMEDIAPLAYER_API __declspec(dllexport)
+#else
+#define LIBMEDIAPLAYER_API __declspec(dllimport)
+#endif
+# elif ( defined(__GNUC__) &&  __GNUC__ >= 4 )
+#define LIBMEDIAPLAYER_API __attribute__((visibility("default")))
+#else // RVC_OS_WIN
+#define LIBMEDIAPLAYER_API
+#endif // RVC_OS_WIN
+
+class libmediaplayer_impl;
+
+class LIBMEDIAPLAYER_API Clibmediaplayer
+{
+public:
+	Clibmediaplayer(CMediaHostApi* pHostApi);
+
+	~Clibmediaplayer();
+
+	int PlayVideo(const char* pVideoDir, const char* pNamePrefix = NULL, int nVideoCount = 1);
+
+	int PlayLocalAudio(const char* pAudioNames);
+
+	int PlayLocalVideo(int nCfgInx, int nWndX, int nWndY, int nWndWidth, int nWndHeight);
+
+	void Close();
+
+	bool checkIsPlay();
+
+	bool checkIsStop();
+
+	int PlayMedia(CMediaPlayConfig& config);
+
+	void SetVolume(int nVolume);
+
+	void PlaySalesRecordVideo(int nWndX, int nWndY, int nWndWidth, int nWndHeight, const char* pVideoDir, const char* pNamePrefix = NULL, int nVideoCount = 1);
+
+	void PlayVideoNotice(int nWndX, int nWndY, int nWndWidth, int nWndHeight, const char* pVideoName);
+
+	bool IsFileValid(const char* pVideoName);
+
+private:
+	libmediaplayer_impl* m_pImpl;
+};
+
+extern "C" LIBMEDIAPLAYER_API int mediaplayer_init();
+extern "C" LIBMEDIAPLAYER_API int mediaplayer_term();

+ 156 - 0
Other/win/libmediaplayer/packet.cpp

@@ -0,0 +1,156 @@
+#include "packet.h"
+
+int packet_queue_init(packet_queue_t *q, CMediaHostApi* hostapi)
+{
+    memset(q, 0, sizeof(packet_queue_t));
+
+    q->mutex = SDL_CreateMutex();
+    if (!q->mutex){
+		hostapi->Debug(MEDIA_LOG_DEBUG, "SDL_CreateMutex(): %s.", SDL_GetError());
+        return AVERROR(ENOMEM);
+    }
+
+    q->packet_cond = SDL_CreateCond();
+    if (!q->packet_cond){
+		hostapi->Debug(MEDIA_LOG_DEBUG, "SDL_CreateCond(): %s.", SDL_GetError());
+        return AVERROR(ENOMEM);
+    }
+    q->abort_flag = 0;
+
+    return 0;
+}
+
+
+// 写队列尾部。pkt是一包还未解码的音频数据
+int packet_queue_put(packet_queue_t *q, AVPacket *pkt, CMediaHostApi* hostapi)
+{
+	AVPacketList* pkt_list = NULL;
+
+    if (av_packet_make_refcounted(pkt) < 0){
+		hostapi->Debug(MEDIA_LOG_DEBUG, "[pkt] is not refrence counted.");
+        return -1;
+    }
+
+    pkt_list = (AVPacketList*)av_malloc(sizeof(AVPacketList));
+    if (!pkt_list){
+        return -1;
+    }
+    
+    pkt_list->pkt = *pkt;
+    pkt_list->next = NULL;
+
+    SDL_LockMutex(q->mutex);
+
+	// 队列为空
+    if (!q->last_pkt){
+        q->first_pkt = pkt_list;
+    }
+    else{
+        q->last_pkt->next = pkt_list;
+    }
+    q->last_pkt = pkt_list;
+    q->nb_packets++;
+    q->packet_queue_size += pkt_list->pkt.size;
+	//hostapi->Debug(MEDIA_LOG_DEBUG, "[%p] packet_queue_put current packet queue len is %d.", q, q->nb_packets);
+    // 发个条件变量的信号:重启等待q->cond条件变量的一个线程
+	SDL_CondSignal(q->packet_cond);
+
+    SDL_UnlockMutex(q->mutex);
+
+    return 0;
+}
+
+// 读队列头部。
+int packet_queue_get(packet_queue_t *q, AVPacket *pkt, int block, CMediaHostApi* hostapi)
+{
+    AVPacketList *p_pkt_node = NULL;
+
+    int ret = -1;
+
+    SDL_LockMutex(q->mutex);
+
+    while (0 == q->abort_flag)
+    {
+        p_pkt_node = q->first_pkt;
+        if (p_pkt_node)             // 队列非空,取一个出来
+        {
+            q->first_pkt = p_pkt_node->next;
+            if (!q->first_pkt)
+            {
+                q->last_pkt = NULL;
+            }
+            q->nb_packets--;
+			//hostapi->Debug(MEDIA_LOG_DEBUG, "[%p] packet_queue_get current packet queue len is %d.", q, q->nb_packets);
+            q->packet_queue_size -= p_pkt_node->pkt.size;
+            *pkt = p_pkt_node->pkt;
+            av_free(p_pkt_node);
+            ret = 1;
+            break;
+        }
+		else if (!block)            // 队列空且阻塞标志无效,则立即退出
+		{
+			ret = 0;
+			break;
+		}
+		else                        // 队列空且阻塞标志有效,则等待
+		{
+			if (SDL_MUTEX_TIMEDOUT == SDL_CondWaitTimeout(q->packet_cond, q->mutex, 1000)){
+				break;
+			}			
+		}
+    }
+    SDL_UnlockMutex(q->mutex);
+
+    return ret;
+}
+
+int packet_queue_put_nullpacket(packet_queue_t *q, int stream_index, CMediaHostApi* hostapi)
+{
+    AVPacket pkt1, *pkt = &pkt1;
+    av_init_packet(pkt);
+    pkt->data = NULL;
+    pkt->size = 0;
+    pkt->stream_index = stream_index;
+    return packet_queue_put(q, pkt, hostapi);
+}
+
+void packet_queue_flush(packet_queue_t *q)
+{
+    AVPacketList *pkt, *pkt1;
+
+    SDL_LockMutex(q->mutex);
+    for (pkt = q->first_pkt; pkt; pkt = pkt1) {
+        pkt1 = pkt->next;
+		if (pkt->pkt.size > 0)
+		{
+			q->packet_queue_size -= pkt->pkt.size;
+			av_packet_unref(&pkt->pkt);
+			av_freep(&pkt);
+			q->nb_packets--;
+		}
+    }
+    q->last_pkt = NULL;
+    q->first_pkt = NULL;
+    q->nb_packets = 0;
+    q->packet_queue_size = 0;
+    q->duration = 0;
+    SDL_UnlockMutex(q->mutex);
+}
+
+void packet_queue_destroy(packet_queue_t *q, CMediaHostApi* m_hostapi)
+{
+    packet_queue_flush(q);
+    SDL_DestroyMutex(q->mutex);
+    SDL_DestroyCond(q->packet_cond);
+	m_hostapi->Debug(MEDIA_LOG_DEBUG, "packet queue destroy.");
+}
+
+void packet_queue_abort(packet_queue_t *q, CMediaHostApi* m_hostapi)
+{
+    SDL_LockMutex(q->mutex);
+    q->abort_flag = 1;
+	m_hostapi->Debug(MEDIA_LOG_DEBUG, "packet_queue_abort, CondSignal cond.");
+    SDL_CondSignal(q->packet_cond);
+    SDL_UnlockMutex(q->mutex);
+}
+

+ 11 - 0
Other/win/libmediaplayer/packet.h

@@ -0,0 +1,11 @@
+#pragma once
+
+#include "player.h"
+
+int packet_queue_init(packet_queue_t *q, CMediaHostApi* m_hostapi);
+int packet_queue_put(packet_queue_t *q, AVPacket *pkt, CMediaHostApi* m_hostapi);
+int packet_queue_get(packet_queue_t *q, AVPacket *pkt, int block, CMediaHostApi* hostapi);
+int packet_queue_put_nullpacket(packet_queue_t *q, int stream_index, CMediaHostApi* hostapi);
+void packet_queue_destroy(packet_queue_t *q, CMediaHostApi* m_hostapi);
+void packet_queue_abort(packet_queue_t *q, CMediaHostApi* m_hostapi);
+

+ 602 - 0
Other/win/libmediaplayer/player.cpp

@@ -0,0 +1,602 @@
+#include <stdio.h>
+//#include <stdbool.h>
+#include <assert.h>
+
+#include "player.h"
+#include "frame.h"
+#include "packet.h"
+#include "demux.h"
+#include "video.h"
+#include "audio.h"
+
+
+static int initialized = 0;
+
+// 返回值:返回上一帧的pts更新值(上一帧pts+流逝的时间)
+double get_clock(play_clock_t *c)
+{
+    if (*c->queue_serial != c->serial)
+    {
+        return NAN;
+    }
+    if (c->paused)
+    {
+        return c->pts;
+    }
+    else
+    {
+        double time = av_gettime_relative() / 1000000.0;
+        double ret = c->pts_drift + time;   // 展开得: c->pts + (time - c->last_updated)
+        return ret;
+    }
+}
+
+
+void set_clock_at(play_clock_t *c, double pts, int serial, double time)
+{
+    c->pts = pts;
+    c->last_updated = time;
+    c->pts_drift = c->pts - time;
+    c->serial = serial;
+}
+
+
+void set_clock(play_clock_t *c, double pts, int serial)
+{
+    double time = av_gettime_relative() / 1000000.0;
+    set_clock_at(c, pts, serial, time);
+}
+
+
+static void set_clock_speed(play_clock_t *c, double speed)
+{
+    set_clock(c, get_clock(c), c->serial);
+    c->speed = speed;
+}
+
+
+void init_clock(play_clock_t *c, int *queue_serial)
+{
+    c->speed = 1.0;
+    c->paused = 0;
+    c->queue_serial = queue_serial;
+    set_clock(c, NAN, -1);
+}
+
+
+static void sync_play_clock_to_slave(play_clock_t *c, play_clock_t *slave)
+{
+    double clock = get_clock(c);
+    double slave_clock = get_clock(slave);
+    if (!isnan(slave_clock) && (isnan(clock) || fabs(clock - slave_clock) > AV_NOSYNC_THRESHOLD))
+        set_clock(c, slave_clock, slave->serial);
+}
+
+/* pause or resume the video */
+static void stream_toggle_pause(player_stat_t *is)
+{
+    if (is->paused)
+    {
+        // 这里表示当前是暂停状态,将切换到继续播放状态。在继续播放之前,先将暂停期间流逝的时间加到frame_timer中
+        is->frame_timer += av_gettime_relative() / 1000000.0 - is->video_clk.last_updated;
+        set_clock(&is->video_clk, get_clock(&is->video_clk), is->video_clk.serial);
+    }
+    is->paused = is->audio_clk.paused = is->video_clk.paused = !is->paused;
+}
+
+
+static void toggle_pause(player_stat_t *is)
+{
+    stream_toggle_pause(is);
+    is->step = 0;
+}
+
+static void toggle_full_screen(player_stat_t* is)
+{
+	SDL_SetWindowFullscreen(is->sdl_video.window, SDL_WINDOW_FULLSCREEN_DESKTOP);
+}
+
+
+CMediaPlayer::CMediaPlayer(CMediaHostApi* pHostApi)
+{
+	m_hostapi = pHostApi;
+	m_player_stat = NULL;
+	m_uvolume = SDL_MIX_MAXVOLUME/2;
+	m_bplaying = false;
+	m_piconpath = NULL;
+	m_paudiodev = NULL;
+	
+	if (NULL != m_hostapi){
+		char str_iconpath[MAX_PATH] = {0};
+		if (0 == pHostApi->GetMediaPlayerIcoPath(str_iconpath, MAX_PATH)){
+			m_piconpath = av_strdup(str_iconpath);
+		}
+
+		char str_audiodev[MAX_PATH] = { 0 };
+		if (0 == pHostApi->GetAudioOutDevName(str_audiodev, MAX_PATH)) {
+			m_paudiodev = av_strdup(str_audiodev);
+		}
+		else {
+			m_paudiodev = NULL;
+		}
+	}
+	else {
+		m_hostapi->Debug(MEDIA_LOG_DEBUG, "new CMediaPlayer failed!");
+	}
+
+	if (initialized){
+		++initialized;
+	}
+	else {
+		if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)){
+			m_hostapi->Debug(MEDIA_LOG_ERROR, "Could not initialize SDL - %s", SDL_GetError());
+			m_hostapi->Debug(MEDIA_LOG_ERROR, "(Did you set the DISPLAY variable?)");
+		}
+		else {
+			initialized++;
+			m_hostapi->Debug(MEDIA_LOG_DEBUG, "initialize SDL success");
+		}
+	}
+}
+
+CMediaPlayer::~CMediaPlayer()
+{
+	if (NULL != m_player_stat) {
+		UnInitialize_Player_Stat();
+	}
+
+	if (NULL != m_piconpath) {
+		av_free(m_piconpath);
+		m_piconpath = NULL;
+	}
+
+	if (NULL != m_paudiodev){
+		av_free(m_paudiodev);
+		m_paudiodev = NULL;
+	}
+
+	if (--initialized == 0){
+		//SDL_Quit();
+		m_hostapi->Debug(MEDIA_LOG_ERROR, "SDL_Quit");
+	}
+}
+
+
+static int audio_volume_callback(int* audiovolume, void* userdata)
+{
+	CMediaPlayer* player = (CMediaPlayer*)userdata;
+	*audiovolume = player->GetVolume();
+	return 0;
+}
+
+
+static int audio_play_finished_callback(void* userdata)
+{
+	CMediaPlayer* player = (CMediaPlayer*)userdata;
+	if (NULL != player){
+		player->SetFinishedFlag();
+	}
+	return 0;
+}
+
+bool CMediaPlayer::SetFinishedFlag()
+{
+	if (NULL != m_player_stat){
+		//m_player_stat->baudio_finished = true;
+		if (NULL != m_hostapi) {
+			m_hostapi->Debug(MEDIA_LOG_ERROR, "%s:%d set baudio_finished to true.", __FUNCTION__, __LINE__);
+		}
+	}
+	else {
+		if (NULL != m_hostapi) {
+			m_hostapi->Debug(MEDIA_LOG_ERROR, "%s:%d m_player_stat is null and set audio finished flag failed.", __FUNCTION__, __LINE__);
+		}
+	}
+	
+	return true;
+}
+
+uint8_t CMediaPlayer::GetVolume()
+{
+	return m_uvolume;
+}
+
+eMediaType_t CMediaPlayer::GetPlayingMediaType()
+{
+	return m_player_stat->eMType;
+}
+
+int CMediaPlayer::Initialize_Player_Stat(rvc_media_player_param_t* pMedia_Player)
+{
+	int iRet = -1;
+
+	if (NULL == pMedia_Player) {
+		return iRet;
+	}
+
+	if (NULL != m_player_stat) {
+		UnInitialize_Player_Stat();
+	}
+
+	m_player_stat = (player_stat_t*)av_mallocz(sizeof(player_stat_t));
+	if (NULL == m_player_stat) {
+		m_hostapi->Debug(MEDIA_LOG_DEBUG, "player_stat_t struct malloc failed!");
+		return iRet;
+	}
+
+	m_player_stat->buser_stop = false;
+	m_player_stat->rvc_hostapi = m_hostapi;
+	m_player_stat->prvc_cb = pMedia_Player->cb;
+	m_player_stat->eMType = pMedia_Player->eType;
+	m_player_stat->eWindType = pMedia_Player->eWindType;
+	m_player_stat->bvice_monitor = pMedia_Player->bvicemonitor;
+	m_player_stat->iDisplayCx = pMedia_Player->idisplaycx;
+	m_player_stat->iDisplayCy = pMedia_Player->idisplaycy;
+	m_player_stat->iDisplayWidth = pMedia_Player->idisplaywidth;
+	m_player_stat->iDisplayHeight = pMedia_Player->idisplayheight;
+	m_player_stat->on_audio_volume = &audio_volume_callback;
+	m_player_stat->on_audio_play_finished = &audio_play_finished_callback;
+	m_player_stat->user_data = this;
+	if (NULL != m_piconpath){
+		m_player_stat->piconpath = av_strdup(m_piconpath);
+	}
+
+	if (NULL != m_paudiodev){
+		m_player_stat->paudiodev = av_strdup(m_paudiodev);
+	}
+	m_player_stat->uVolume = m_uvolume;
+	m_player_stat->uFilesCount = pMedia_Player->uFilesCount;
+	m_player_stat->icurrent_index = 0;
+	m_player_stat->iaudio_dec_index = 0;
+	for (size_t i = 0; i < pMedia_Player->uFilesCount; i++){
+		memcpy(m_player_stat->strPlayLists[i], pMedia_Player->strPlayLists[i], strlen(pMedia_Player->strPlayLists[i]));
+	}
+
+	if (0 == pMedia_Player->uFilesCount || NULL == m_player_stat->rvc_hostapi || NULL == m_player_stat->prvc_cb || NULL == m_player_stat->piconpath) {
+		UnInitialize_Player_Stat();
+		return iRet;
+	}
+	else{
+		iRet = 0;
+	}
+
+	return iRet;
+}
+
+
+int CMediaPlayer::UnInitialize_Player_Stat()
+{
+	/* XXX: use a special url_shutdown call to abort parse cleanly */
+	if (NULL == m_player_stat) {
+		return -1;
+	}
+
+	if (NULL != m_player_stat->read_tid){
+		SDL_WaitThread(m_player_stat->read_tid, NULL);
+		m_player_stat->read_tid = NULL;
+		m_hostapi->Debug(MEDIA_LOG_DEBUG, "demux thread finished.");
+	}
+
+	for (int index = 0; index < m_player_stat->uFilesCount; index++) {
+		if (NULL != m_player_stat->p_fmt_ctx[index]) {
+			avformat_close_input(&m_player_stat->p_fmt_ctx[index]);
+			avformat_free_context(m_player_stat->p_fmt_ctx[index]);
+			m_player_stat->p_fmt_ctx[index] = NULL;
+		}
+
+		if (NULL != m_player_stat->p_acodec_ctx[index]) {
+			avcodec_close(m_player_stat->p_acodec_ctx[index]);
+			avcodec_free_context(&m_player_stat->p_acodec_ctx[index]);
+		}
+
+		if (NULL != m_player_stat->p_vcodec_ctx[index]) {
+			avcodec_close(m_player_stat->p_vcodec_ctx[index]);
+			avcodec_free_context(&m_player_stat->p_vcodec_ctx[index]);
+		}
+	}
+	
+	SDL_DestroyCond(m_player_stat->continue_read_thread);
+	SDL_DestroyCond(m_player_stat->audio_play_cond);
+	SDL_DestroyMutex(m_player_stat->audio_play_wait_mutex);
+
+	if (m_player_stat->eMType == eVideo_Type) {
+		for (int index = 0; index < m_player_stat->uFilesCount; index++) {
+			if (NULL != m_player_stat->img_convert_ctx[index]) {
+				sws_freeContext(m_player_stat->img_convert_ctx[index]);
+				m_player_stat->img_convert_ctx[index] = NULL;
+			}
+			if (NULL != m_player_stat->p_frm_yuv[index]) {
+				av_frame_free(&m_player_stat->p_frm_yuv[index]);
+				av_frame_unref(m_player_stat->p_frm_yuv[index]);
+			}
+			if (NULL != m_player_stat->p_video_buffer[index]) {
+				av_free(m_player_stat->p_video_buffer[index]);
+				m_player_stat->p_video_buffer[index] = NULL;
+			}
+		}
+	}
+
+	packet_queue_destroy(&m_player_stat->video_pkt_queue, m_hostapi);
+	packet_queue_destroy(&m_player_stat->audio_pkt_queue, m_hostapi);
+
+	///* free all pictures */
+	frame_queue_destory(&m_player_stat->video_frm_queue);
+	frame_queue_destory(&m_player_stat->audio_frm_queue);
+
+	m_hostapi->Debug(MEDIA_LOG_DEBUG, "av_free player_stat_t.");
+
+	swr_free(&m_player_stat->audio_swr_ctx);
+
+	if (NULL != m_player_stat->audio_frm_rwr) {
+		av_free(m_player_stat->audio_frm_rwr);
+		m_player_stat->audio_frm_rwr = NULL;
+	}
+
+	if (m_player_stat->piconpath){
+		av_free(m_player_stat->piconpath);
+		m_player_stat->piconpath = NULL;
+	}
+
+	if (m_player_stat->paudiodev) {
+		av_free(m_player_stat->paudiodev);
+		m_player_stat->paudiodev = NULL;
+	}
+
+	if (m_player_stat->straudiodev) {
+		av_free(m_player_stat->straudiodev);
+		m_player_stat->straudiodev = NULL;
+	}
+
+	av_free(m_player_stat);
+	m_player_stat = NULL;
+
+	return 0;
+}
+
+int CMediaPlayer::GetViceVideoDisplayInfo(int* icx, int* icy, int* iwidth, int* iheight)
+{
+	int iRet = -1;
+	size_t uCount = SDL_GetNumVideoDisplays();
+	m_hostapi->Debug(MEDIA_LOG_DEBUG, "VideoDisplays Number is %d.", uCount);
+	SDL_DisplayMode dispalymode[RVC_MAX_DISPLAYNUM] = { 0 };
+	for (size_t i = 0; i < uCount && i < RVC_MAX_DISPLAYNUM; i++) {
+		SDL_GetDesktopDisplayMode(i, &dispalymode[i]);
+		m_hostapi->Debug(MEDIA_LOG_DEBUG, "VideoDisplays{%d} format = %d", i, dispalymode[i].format);
+		m_hostapi->Debug(MEDIA_LOG_DEBUG, "VideoDisplays{%d} w = %d", i, dispalymode[i].w);
+		m_hostapi->Debug(MEDIA_LOG_DEBUG, "VideoDisplays{%d} h = %d", i, dispalymode[i].h);
+		m_hostapi->Debug(MEDIA_LOG_DEBUG, "VideoDisplays{%d} refresh_rate = %d", i, dispalymode[i].refresh_rate);
+	}
+	if (uCount > 1) {
+		*icx = dispalymode[0].w;
+		*icy = 0;
+		*iwidth = dispalymode[1].w;
+		*iheight = dispalymode[1].h;
+		iRet = 0;
+	}
+
+	return iRet;
+}
+
+int CMediaPlayer::InitParam(rvc_media_player_param_t* pMedia_Player)
+{
+	int iRet = -1;
+
+	if (0 != Initialize_Player_Stat(pMedia_Player)){
+		return iRet;
+	}
+	m_hostapi->Debug(MEDIA_LOG_DEBUG, "initialize player stat success.");
+	/* start video display */
+	if (frame_queue_init(&m_player_stat->video_frm_queue, &m_player_stat->video_pkt_queue, VIDEO_PICTURE_QUEUE_SIZE, 1) < 0 ||
+		frame_queue_init(&m_player_stat->audio_frm_queue, &m_player_stat->audio_pkt_queue, SAMPLE_QUEUE_SIZE, 1) < 0)
+	{
+		m_hostapi->Debug(MEDIA_LOG_DEBUG, "media frame queue init failed!");
+		UnInitialize_Player_Stat();
+		return iRet;
+	}
+	else{
+		m_hostapi->Debug(MEDIA_LOG_DEBUG, "frame queue init success!");
+	}
+
+	/* init packet queue */
+	if (packet_queue_init(&m_player_stat->video_pkt_queue, m_hostapi) < 0 ||
+		packet_queue_init(&m_player_stat->audio_pkt_queue, m_hostapi) < 0)
+	{
+		m_hostapi->Debug(MEDIA_LOG_DEBUG, "media packet queue init failed!");
+		UnInitialize_Player_Stat();
+		return iRet;
+	}
+	else{
+		m_hostapi->Debug(MEDIA_LOG_DEBUG, "packet queue audio[%p] video[%p] init success!", &m_player_stat->audio_pkt_queue, &m_player_stat->video_pkt_queue);
+	}
+
+	/* put end pkt into packet queue */
+	AVPacket flush_pkt = {0};
+	flush_pkt.data = NULL;
+	packet_queue_put(&m_player_stat->video_pkt_queue, &flush_pkt, m_hostapi);
+	packet_queue_put(&m_player_stat->audio_pkt_queue, &flush_pkt, m_hostapi);
+
+	if (!(m_player_stat->continue_read_thread = SDL_CreateCond()) || !(m_player_stat->audio_play_cond = SDL_CreateCond())){
+		m_hostapi->Debug(MEDIA_LOG_DEBUG, "SDL_CreateCond(): %s.", SDL_GetError());
+		UnInitialize_Player_Stat();
+		return iRet;
+	}
+
+	m_player_stat->audio_play_wait_mutex = SDL_CreateMutex();
+
+	init_clock(&m_player_stat->video_clk, &m_player_stat->video_pkt_queue.serial);
+	init_clock(&m_player_stat->audio_clk, &m_player_stat->audio_pkt_queue.serial);
+
+	if (m_player_stat->bvice_monitor){
+		if (0 == GetViceVideoDisplayInfo(&m_player_stat->iDisplayCx, &m_player_stat->iDisplayCy, &m_player_stat->iDisplayWidth, &m_player_stat->iDisplayHeight)){
+			m_hostapi->Debug(MEDIA_LOG_DEBUG, "Get Vice Video Display Info Success.");
+		}
+	}
+	
+	m_hostapi->Debug(MEDIA_LOG_DEBUG, "display cx is %d, cy is %d, width is %d, height is %d.", m_player_stat->iDisplayCx, m_player_stat->iDisplayCy, m_player_stat->iDisplayWidth, m_player_stat->iDisplayHeight);
+
+	iRet = 0;
+
+	return iRet;
+}
+
+
+int CMediaPlayer::SetVolume(uint8_t uVolume)
+{
+	int iRet = -1;
+	
+	m_uvolume = uVolume * SDL_MIX_MAXVOLUME/100;
+
+	m_hostapi->Debug(MEDIA_LOG_DEBUG, "set audio volume(%d) to %d.", uVolume, m_uvolume);
+
+	iRet = 0;
+
+	return iRet;
+}
+
+
+bool CMediaPlayer::GetPlayingFlag()
+{
+	return m_bplaying;
+}
+
+
+int CMediaPlayer::StartMediaPlay()
+{
+	int iRet = -1;
+	if (0 == open_demux(m_player_stat)){
+		m_hostapi->Debug(MEDIA_LOG_DEBUG, "open_demux function call success.");
+	}
+	else{
+		m_hostapi->Debug(MEDIA_LOG_ERROR, "open_demux function call failed.");
+		av_free(m_player_stat);
+		m_player_stat = NULL;
+		iRet = -3;
+		return iRet;
+	}
+
+	m_hostapi->Debug(MEDIA_LOG_DEBUG, "set playing flag to true.");
+	m_bplaying = true;
+
+	if (eVideo_Type == m_player_stat->eMType) {
+		open_video(m_player_stat);
+	}
+
+	open_audio(m_player_stat);
+
+	m_hostapi->Debug(MEDIA_LOG_DEBUG, "after open_audio function.");
+
+	if (NULL != m_player_stat->audio_decode_tid) {
+		SDL_WaitThread(m_player_stat->audio_decode_tid, NULL);
+		m_player_stat->audio_decode_tid = NULL;
+		m_hostapi->Debug(MEDIA_LOG_DEBUG, "audio decode thread finished.");
+	}
+
+	if (m_player_stat->eMType == eVideo_Type) {
+		if (NULL != m_player_stat->video_decode_tid) {
+			SDL_WaitThread(m_player_stat->video_decode_tid, NULL);
+			m_player_stat->video_decode_tid = NULL;
+			m_hostapi->Debug(MEDIA_LOG_DEBUG, "video decode thread finished.");
+		}
+
+		if (NULL != m_player_stat->video_playing_tid) {
+			SDL_WaitThread(m_player_stat->video_playing_tid, NULL);
+			m_player_stat->video_playing_tid = NULL;
+			m_hostapi->Debug(MEDIA_LOG_DEBUG, "video playing thread finished.");
+		}
+	}
+
+	ExitMediaPlayingThread();
+
+	iRet = 0;
+	
+	return iRet;
+}
+
+
+int CMediaPlayer::StopMediaPlay()
+{
+	int iRet = -1;
+
+	m_hostapi->Debug(MEDIA_LOG_DEBUG, "StopMediaPlay called.");
+
+	if (NULL == m_player_stat) {
+		return iRet;
+	}
+
+	packet_queue_abort(&m_player_stat->video_pkt_queue, m_hostapi);
+	packet_queue_abort(&m_player_stat->audio_pkt_queue, m_hostapi);
+
+	frame_queue_signal(&m_player_stat->video_frm_queue);
+	frame_queue_signal(&m_player_stat->audio_frm_queue);
+
+	m_hostapi->Debug(MEDIA_LOG_ERROR, "%s:%d set m_player_stat media finished flag to true.", __FUNCTION__, __LINE__);
+	m_player_stat->buser_stop = true;
+
+	if (m_bplaying) {
+		m_hostapi->Debug(MEDIA_LOG_DEBUG, "user stop audio play set SDL_PauseAudioDevice param to 1.");
+		if (0 != audio_dev){
+			SDL_PauseAudioDevice(audio_dev, 1);
+		}
+	}
+
+	iRet = 0;
+
+	return iRet;
+}
+
+
+int CMediaPlayer::ExitMediaPlayingThread()
+{
+	int iRet = -1;
+
+	if (NULL == m_player_stat){
+		return iRet;
+	}
+
+	m_hostapi->Debug(MEDIA_LOG_DEBUG, "enter ExitMediaPlayingThread call.");
+
+	if(eVideo_Type == m_player_stat->eMType){
+		if (m_player_stat->sdl_video.texture) {
+			SDL_DestroyTexture(m_player_stat->sdl_video.texture);
+		}
+
+		if (m_player_stat->sdl_video.renderer){
+			SDL_DestroyRenderer(m_player_stat->sdl_video.renderer);
+		}
+		
+		if (m_player_stat->sdl_video.window){
+			SDL_DestroyWindow(m_player_stat->sdl_video.window);
+			m_hostapi->Debug(MEDIA_LOG_ERROR, "Destroy Window.");
+		}
+	}
+
+	if (m_player_stat->prvc_cb){
+		m_hostapi->Debug(MEDIA_LOG_DEBUG, "cb_play_media_finished callback.");
+		m_player_stat->prvc_cb->cb_play_media_finished(m_player_stat->prvc_cb->user_data);
+	}
+
+	UnInitialize_Player_Stat();
+
+	m_bplaying = false;
+
+	iRet = 0;
+	
+	m_hostapi->Debug(MEDIA_LOG_DEBUG, "Leave ExitMediaPlayingThread call.");
+
+	return iRet;
+}
+
+
+int64_t CMediaPlayer::GetMediaPlayingThreadId()
+{
+	int64_t iRet = 0;
+	if (NULL != m_player_stat)
+	{
+		if (NULL != m_player_stat->read_tid)
+		{
+			iRet = SDL_GestureID(m_player_stat->read_tid);
+		}
+	}
+
+	return iRet;
+}

+ 302 - 0
Other/win/libmediaplayer/player.h

@@ -0,0 +1,302 @@
+#pragma once
+
+#include <stdio.h>
+#include <stdint.h>
+//#include <stdbool.h>
+#include "idatastruct.h"
+
+
+#ifndef INT64_C
+#define INT64_C(c) (c##LL) 
+#define UINT64_C(c) (c##UL) 
+#endif
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif // __cplusplus
+
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#include <libswscale/swscale.h>
+#include <libswresample/swresample.h>
+#include <libavutil/frame.h>
+#include <libavutil/time.h>
+#include <libavutil/imgutils.h>
+#include <libavutil/mem.h>
+
+#if defined(_WIN32)
+#include <SDL.h>
+#include <SDL_video.h>
+#include <SDL_render.h>
+#include <SDL_rect.h>
+#include <SDL_mutex.h>
+#include <SDL_syswm.h>
+#else
+#include <SDL2/SDL.h>
+#include <SDL2/SDL_video.h>
+#include <SDL2/SDL_render.h>
+#include <SDL2/SDL_rect.h>
+#include <SDL2/SDL_mutex.h>
+#endif
+
+#ifdef __cplusplus
+}
+#endif // __cplusplus
+
+
+/* no AV sync correction is done if below the minimum AV sync threshold */
+#define AV_SYNC_THRESHOLD_MIN 0.04
+/* AV sync correction is done if above the maximum AV sync threshold */
+#define AV_SYNC_THRESHOLD_MAX 0.1
+/* If a frame duration is longer than this, it will not be duplicated to compensate AV sync */
+#define AV_SYNC_FRAMEDUP_THRESHOLD 0.1
+/* no AV correction is done if too big error */
+#define AV_NOSYNC_THRESHOLD 10.0
+
+/* polls for possible required screen refresh at least this often, should be less than 1/fps */
+#define REFRESH_RATE 0.01
+
+#define SDL_AUDIO_BUFFER_SIZE 1024
+#define MAX_AUDIO_FRAME_SIZE 192000
+
+#define MAX_QUEUE_SIZE (15 * 1024 * 1024)
+#define MIN_FRAMES 25
+
+/* Minimum SDL audio buffer size, in samples. */
+#define SDL_AUDIO_MIN_BUFFER_SIZE 512
+/* Calculate actual buffer size keeping in mind not cause too frequent audio callbacks */
+#define SDL_AUDIO_MAX_CALLBACKS_PER_SEC 30
+
+#define VIDEO_PICTURE_QUEUE_SIZE 3
+#define SUBPICTURE_QUEUE_SIZE 16
+#define SAMPLE_QUEUE_SIZE 9
+#define FRAME_QUEUE_SIZE FFMAX(SAMPLE_QUEUE_SIZE, FFMAX(VIDEO_PICTURE_QUEUE_SIZE, SUBPICTURE_QUEUE_SIZE))
+
+
+#define FF_QUIT_EVENT    (SDL_USEREVENT + 2)
+
+#ifndef RVC_MAX_DISPLAYNUM
+#define RVC_MAX_DISPLAYNUM 5
+#endif
+
+#ifndef RVC_DEFAULT_SLEEP_TIME
+#define RVC_DEFAULT_SLEEP_TIME 10*1000
+#endif
+
+
+#ifndef isnan 
+#define isnan(x) ((x)!=(x)) 
+#endif
+
+
+typedef struct play_clock_s{
+    double pts;                     // 当前帧(待播放)显示时间戳,播放后,当前帧变成上一帧
+    double pts_drift;               // 当前帧显示时间戳与当前系统时钟时间的差值
+    double last_updated;            // 当前时钟(如视频时钟)最后一次更新时间,也可称当前时钟时间
+    double speed;                   // 时钟速度控制,用于控制播放速度
+    int serial;                     // 播放序列,所谓播放序列就是一段连续的播放动作,一个seek操作会启动一段新的播放序列
+    int paused;                     // 暂停标志
+    int *queue_serial;              // 指向packet_serial
+}play_clock_t;
+
+typedef struct sdl_video_s{
+    SDL_Window *window; 
+    SDL_Renderer *renderer;
+    SDL_Texture *texture;
+    SDL_Rect rect;
+}sdl_video_t;
+
+typedef struct packet_queue_s{
+    AVPacketList *first_pkt, *last_pkt;
+    int nb_packets;                 // 队列中packet的数量
+    int packet_queue_size;          // 队列所占内存空间大小
+    int64_t duration;               // 队列中所有packet总的播放时长
+    int abort_flag;
+    int serial;                     // 播放序列,所谓播放序列就是一段连续的播放动作,一个seek操作会启动一段新的播放序列
+    SDL_mutex *mutex;
+    SDL_cond * packet_cond;
+}packet_queue_t;
+
+/* Common struct for handling all types of decoded data and allocated render buffers. */
+typedef struct frame_s{
+    AVFrame *frame;
+    int serial;
+    double pts;           /* presentation timestamp for the frame */
+    double duration;      /* estimated duration of the frame */
+    int64_t pos;          // frame对应的packet在输入文件中的地址偏移
+    int width;
+    int height;
+    int format;
+    AVRational sar;
+    int uploaded;
+    int flip_v;
+}frame_t;
+
+typedef struct frame_queue_s{
+    frame_t queue[FRAME_QUEUE_SIZE];
+    int rindex;                     // 读索引。待播放时读取此帧进行播放,播放后此帧成为上一帧
+    int windex;                     // 写索引
+    int size;                       // 总帧数
+    int max_size;                   // 队列可存储最大帧数
+    int keep_last;
+    int rindex_shown;               // 当前是否有帧在显示
+    SDL_mutex *frame_mutex;
+    SDL_cond *frame_cond;
+    packet_queue_t *pktq;           // 指向对应的packet_queue
+}frame_queue_t;
+
+
+typedef struct play_media_callback_s {
+	void (*cb_play_media_finished)(void* user_data);
+	int (*cb_playing_audiodata)(audio_param_t* param, const void* input, unsigned long uaudiolen, void* user_data);
+	void* user_data;
+}play_media_callback_t;
+
+typedef enum eMediaType_s {
+	eAudio_Type,
+	eVideo_Type,
+	eImage_Type
+}eMediaType_t;
+
+static const char* Media_Type_Table[] = {
+	"Audio",
+	"Video",
+	"Image"
+};
+
+typedef enum eWindType_s {
+	eVideoSize_Type,
+	eFullScreen_Type,
+	eSpecified_Type
+}eWindType_t;
+
+typedef struct player_stat_s{
+    AVFormatContext *p_fmt_ctx[MAX_FILECOUNT];
+    AVStream *p_audio_stream[MAX_FILECOUNT];
+    AVStream *p_video_stream[MAX_FILECOUNT];
+    AVCodecContext *p_acodec_ctx[MAX_FILECOUNT];
+    AVCodecContext *p_vcodec_ctx[MAX_FILECOUNT];
+
+    int audio_idx[MAX_FILECOUNT];
+    int video_idx[MAX_FILECOUNT];
+
+    sdl_video_t sdl_video;
+
+    play_clock_t audio_clk;                   // 音频时钟
+    play_clock_t video_clk;                   // 视频时钟
+    double frame_timer;
+
+    packet_queue_t audio_pkt_queue;
+    packet_queue_t video_pkt_queue;
+
+    frame_queue_t audio_frm_queue;
+    frame_queue_t video_frm_queue;
+
+    struct SwsContext *img_convert_ctx[MAX_FILECOUNT];
+    struct SwrContext *audio_swr_ctx;
+    AVFrame *p_frm_yuv[MAX_FILECOUNT];
+	uint8_t *p_video_buffer[MAX_FILECOUNT];
+
+    audio_param_t audio_param_src;
+    audio_param_t audio_param_tgt;
+    int audio_hw_buf_size;              // SDL音频缓冲区大小(单位字节)
+    uint8_t *p_audio_frm;               // 指向待播放的一帧音频数据,指向的数据区将被拷入SDL音频缓冲区。若经过重采样则指向audio_frm_rwr,否则指向frame中的音频
+    uint8_t *audio_frm_rwr;             // 音频重采样的输出缓冲区
+    unsigned int audio_frm_size;        // 待播放的一帧音频数据(audio_buf指向)的大小
+    unsigned int audio_frm_rwr_size;    // 申请到的音频缓冲区audio_frm_rwr的实际尺寸
+    int audio_cp_index;                 // 当前音频帧中已拷入SDL音频缓冲区的位置索引(指向第一个待拷贝字节)
+    int audio_write_buf_size;           // 当前音频帧中尚未拷入SDL音频缓冲区的数据量,audio_frm_size = audio_cp_index + audio_write_buf_size
+    double audio_clock;
+    int audio_clock_serial;
+    
+    int paused;
+    int step;
+
+	volatile bool buser_stop;
+
+    SDL_cond *continue_read_thread;
+    SDL_Thread *read_tid;           // demux解复用线程
+	SDL_Thread* audio_decode_tid;	// 音频解码线程
+	volatile bool baudio_decode_finished;
+	SDL_Thread* video_decode_tid;   // 视频解码线程
+	volatile bool bvideo_decode_finished;
+	SDL_Thread* video_playing_tid;  // 视频播放线程
+	SDL_cond* audio_play_cond;
+	SDL_mutex* audio_play_wait_mutex;
+	
+	play_media_callback_t* prvc_cb;	// 播放状态回调函数
+	char* piconpath;				// icon图标路径
+	char* paudiodev;				// 音频输出设备名
+	eMediaType_t eMType;			// 媒体类型
+	eWindType_t eWindType;			// 视频框大小类型
+	volatile uint8_t uVolume;		// 音量大小1-128
+	char* straudiodev;				// 获取到的音频设备名
+	CMediaHostApi* rvc_hostapi;
+	
+	char strPlayLists[MAX_FILECOUNT][MAX_PATH];		//播放列表,全路径
+	uint8_t uFilesCount;							//播放文件数
+	volatile int icurrent_index;					//当前播放文件索引
+	volatile int iaudio_dec_index;					//当前音频解码器索引
+	bool bCirclePlay;
+	bool bvice_monitor;
+	int iDisplayCx;
+	int iDisplayCy;
+	int iDisplayWidth;
+	int iDisplayHeight;
+	int (*on_audio_volume)(int* audiodata, void* user_data);
+	int (*on_audio_play_finished)(void* user_data);
+	void* user_data;
+}player_stat_t;
+
+
+typedef struct rvc_media_player_param_s{
+	char* p_input_file;
+	eMediaType_t eType;
+	eWindType_t eWindType;
+	int idisplaycx;
+	int idisplaycy;
+	int idisplaywidth;
+	int idisplayheight;
+	bool bvicemonitor;
+	char strPlayLists[MAX_FILECOUNT][MAX_PATH];		//播放列表,全路径
+	uint8_t uFilesCount;								//播放文件数
+	play_media_callback_t* cb;
+}rvc_media_player_param_t;
+
+
+double get_clock(play_clock_t *c);
+void set_clock_at(play_clock_t *c, double pts, int serial, double time);
+void set_clock(play_clock_t *c, double pts, int serial);
+
+static SDL_AudioDeviceID audio_dev;
+
+class CMediaPlayer
+{
+public:
+	CMediaPlayer(CMediaHostApi* pHostApi);
+	~CMediaPlayer();
+
+	int InitParam(rvc_media_player_param_t* pMedia_Player);
+	int Initialize_Player_Stat(rvc_media_player_param_t* pMedia_Player);
+	int UnInitialize_Player_Stat();
+	int SetVolume(uint8_t uVolume);
+	bool GetPlayingFlag();
+	int StartMediaPlay();
+	int StopMediaPlay();
+	int ExitMediaPlayingThread();
+	int64_t GetMediaPlayingThreadId();
+	int GetViceVideoDisplayInfo(int* icx, int* icy, int* iwidth, int* iheight);
+	uint8_t GetVolume();
+	bool SetFinishedFlag();
+	eMediaType_t GetPlayingMediaType();
+
+private:
+	player_stat_t* m_player_stat;
+	bool m_bplaying;
+	uint8_t m_uvolume;
+	CMediaHostApi* m_hostapi;
+	char* m_piconpath;				// ico图标路径
+	char* m_paudiodev;				// 音频输出设备
+};
+

+ 8 - 0
Other/win/libmediaplayer/stdafx.cpp

@@ -0,0 +1,8 @@
+// stdafx.cpp : source file that includes just the standard includes
+// libmediaplayer.pch will be the pre-compiled header
+// stdafx.obj will contain the pre-compiled type information
+
+#include "stdafx.h"
+
+// TODO: reference any additional headers you need in STDAFX.H
+// and not in this file

+ 16 - 0
Other/win/libmediaplayer/stdafx.h

@@ -0,0 +1,16 @@
+// stdafx.h : include file for standard system include files,
+// or project specific include files that are used frequently, but
+// are changed infrequently
+//
+
+#pragma once
+
+#include "targetver.h"
+
+#define WIN32_LEAN_AND_MEAN             // Exclude rarely-used stuff from Windows headers
+// Windows Header Files:
+#include <windows.h>
+
+
+
+// TODO: reference additional headers your program requires here

+ 8 - 0
Other/win/libmediaplayer/targetver.h

@@ -0,0 +1,8 @@
+#pragma once
+
+// Including SDKDDKVer.h defines the highest available Windows platform.
+
+// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and
+// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h.
+
+#include <SDKDDKVer.h>

+ 772 - 0
Other/win/libmediaplayer/video.cpp

@@ -0,0 +1,772 @@
+#include "video.h"
+#include "packet.h"
+#include "frame.h"
+#include "player.h"
+
+//Refresh Event
+#ifndef REFRESH_EVENT
+#define REFRESH_EVENT  (SDL_USEREVENT + 1)
+#endif
+
+#ifndef RVC_DEFAULT_DELAY_TIME
+#define RVC_DEFAULT_DELAY_TIME 1
+#endif
+
+static int queue_picture(player_stat_t *is, AVFrame *src_frame, double pts, double duration, int64_t pos)
+{
+    frame_t *vp = NULL;
+
+	if (!(vp = frame_queue_peek_writable(&is->video_frm_queue))) {
+		return -1;
+	}
+
+    vp->sar = src_frame->sample_aspect_ratio;
+    vp->uploaded = 0;
+
+    vp->width = src_frame->width;
+    vp->height = src_frame->height;
+    vp->format = src_frame->format;
+
+    vp->pts = pts;
+    vp->duration = duration;
+    vp->pos = pos;
+    //vp->serial = serial;
+
+    //set_default_window_size(vp->width, vp->height, vp->sar);
+
+    // 将AVFrame拷入队列相应位置
+    av_frame_move_ref(vp->frame, src_frame);
+    // 更新队列计数及写索引
+    frame_queue_push(&is->video_frm_queue);
+
+    return 0;
+}
+
+
+// 从packet_queue中取一个packet,解码生成frame
+static int video_decode_frame(AVCodecContext *p_codec_ctx, packet_queue_t *p_pkt_queue, AVFrame *frame, CMediaHostApi* hostapi)
+{
+    int ret = -1;
+    
+    while (0 == p_pkt_queue->abort_flag)
+    {
+		AVPacket pkt = {0};
+		av_init_packet(&pkt);
+        while (0 == p_pkt_queue->abort_flag)
+        {
+            // 3. 从解码器接收frame
+            // 3.1 一个视频packet含一个视频frame
+            //     解码器缓存一定数量的packet后,才有解码后的frame输出
+            //     frame输出顺序是按pts的顺序,如IBBPBBP
+            //     frame->pkt_pos变量是此frame对应的packet在视频文件中的偏移地址,值同pkt.pos
+
+            ret = avcodec_receive_frame(p_codec_ctx, frame);
+            if (ret < 0)
+            {
+                if (ret == AVERROR_EOF)
+                {
+					hostapi->Debug(MEDIA_LOG_DEBUG, "video avcodec_receive_frame(): the decoder has been fully flushed.");
+					avcodec_flush_buffers(p_codec_ctx);
+                    return 0;
+                }
+                else if (ret == AVERROR(EAGAIN))
+                {
+					//hostapi->Debug(MEDIA_LOG_DEBUG,"video avcodec_receive_frame(): output is not available in this state - " "user must try to send new input");
+                    break;
+                }
+                else
+                {
+					hostapi->Debug(MEDIA_LOG_DEBUG, "video avcodec_receive_frame(): other errors.");
+					continue;
+                }
+            }
+            else
+            {
+                frame->pts = frame->best_effort_timestamp;
+                //frame->pts = frame->pkt_dts;
+                return 1;   // 成功解码得到一个视频帧,则返回
+            }
+        }
+
+        // 1. 取出一个packet。使用pkt对应的serial赋值给d->pkt_serial
+        if (packet_queue_get(p_pkt_queue, &pkt, true, hostapi) < 0){
+            return -1;
+        }
+		else{
+			//hostapi->Debug(MEDIA_LOG_DEBUG, "[%p] packet_queue_get pkt and pkt.dts = %u.", p_pkt_queue, pkt.dts);
+		}
+
+        if (pkt.data == NULL){
+            // 复位解码器内部状态/刷新内部缓冲区。
+            avcodec_flush_buffers(p_codec_ctx);
+        }
+        else{
+            // 2. 将packet发送给解码器
+            //    发送packet的顺序是按dts递增的顺序,如IPBBPBB
+            //    pkt.pos变量可以标识当前packet在视频文件中的地址偏移
+			int isend_ret = -1;
+			isend_ret = avcodec_send_packet(p_codec_ctx, &pkt);
+			
+			if (0 != isend_ret){
+				if (AVERROR(EAGAIN) == isend_ret) {
+					hostapi->Debug(MEDIA_LOG_DEBUG, "receive_frame and send_packet both returned EAGAIN, which is an API violation.");
+				}
+				else if (AVERROR_EOF == isend_ret) {
+					hostapi->Debug(MEDIA_LOG_DEBUG, "the decoder has been flushed, and no new packets can be sent to it");
+				}
+				else if (AVERROR(EINVAL) == isend_ret) {
+					hostapi->Debug(MEDIA_LOG_DEBUG, "codec not opened, it is an encoder, or requires flush");
+				}
+				else if (AVERROR(ENOMEM) == isend_ret) {
+					hostapi->Debug(MEDIA_LOG_DEBUG, "failed to add packet to internal queue, or similar");
+				}
+				else {
+					hostapi->Debug(MEDIA_LOG_DEBUG, "legitimate decoding errors and avcodec_send_packet result is %d.", isend_ret);
+				}
+			}
+            av_packet_unref(&pkt);
+        }
+    }
+}
+
+// 将视频包解码得到视频帧,然后写入picture队列
+static int video_decode_thread(void *arg)
+{
+    player_stat_t *is = (player_stat_t *)arg;
+    AVFrame *p_frame = av_frame_alloc();
+    double pts = 0.0;
+    double duration = 0.0;
+    int ret=0;
+    int got_picture = 0;
+
+    if (p_frame == NULL){
+		is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "av_frame_alloc() for p_frame failed.");
+		return AVERROR(ENOMEM);
+    }
+
+    while (false == is->buser_stop)
+    {
+		AVRational tb = is->p_video_stream[is->icurrent_index]->time_base;
+		AVRational frame_rate = av_guess_frame_rate(is->p_fmt_ctx[is->icurrent_index], is->p_video_stream[is->icurrent_index], NULL);
+        got_picture = video_decode_frame(is->p_vcodec_ctx[is->icurrent_index], &is->video_pkt_queue, p_frame, is->rvc_hostapi);
+        if (got_picture < 0){
+			is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "video_decode_frame < 0, goto end and set video_finished flag to true.");
+            goto exit;
+        }
+
+		AVRational tbdata = { frame_rate.den, frame_rate.num };
+		duration = (frame_rate.num && frame_rate.den ? av_q2d(tbdata) : 0);			// 当前帧播放时长
+        //duration = (frame_rate.num && frame_rate.den ? av_q2d((AVRational){frame_rate.den, frame_rate.num}) : 0);   // 当前帧播放时长
+        pts = (p_frame->pts == AV_NOPTS_VALUE) ? NAN : p_frame->pts * av_q2d(tb);   // 当前帧显示时间戳
+        ret = queue_picture(is, p_frame, pts, duration, p_frame->pkt_pos);			// 将当前帧压入frame_queue
+        av_frame_unref(p_frame);
+
+        if (ret < 0){
+			is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "queue_picture return -1, goto end and set video_finished flag to true.");
+            goto exit;
+        }
+		else{
+			//is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "queue_picture success!");
+		}
+    }
+
+exit:
+    av_frame_free(&p_frame);
+	is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "video decode thread exit, thread id is %u, and user_stop flag is %s.", SDL_ThreadID(), is->buser_stop ? "true" : "false");
+	is->bvideo_decode_finished = true;
+
+    return 0;
+}
+
+// 根据视频时钟与同步时钟(如音频时钟)的差值,校正delay值,使视频时钟追赶或等待同步时钟
+// 输入参数delay是上一帧播放时长,即上一帧播放后应延时多长时间后再播放当前帧,通过调节此值来调节当前帧播放快慢
+// 返回值delay是将输入参数delay经校正后得到的值
+static double compute_target_delay(double delay, player_stat_t *is)
+{
+    double sync_threshold, diff = 0;
+
+    /* update delay to follow master synchronisation source */
+
+    /* if video is slave, we try to correct big delays by
+       duplicating or deleting a frame */
+    // 视频时钟与同步时钟(如音频时钟)的差异,时钟值是上一帧pts值(实为:上一帧pts + 上一帧至今流逝的时间差)
+    diff = get_clock(&is->video_clk) - get_clock(&is->audio_clk);
+    // delay是上一帧播放时长:当前帧(待播放的帧)播放时间与上一帧播放时间差理论值
+    // diff是视频时钟与同步时钟的差值
+
+    /* skip or repeat frame. We take into account the
+       delay to compute the threshold. I still don't know
+       if it is the best guess */
+    // 若delay < AV_SYNC_THRESHOLD_MIN,则同步域值为AV_SYNC_THRESHOLD_MIN
+    // 若delay > AV_SYNC_THRESHOLD_MAX,则同步域值为AV_SYNC_THRESHOLD_MAX
+    // 若AV_SYNC_THRESHOLD_MIN < delay < AV_SYNC_THRESHOLD_MAX,则同步域值为delay
+    sync_threshold = FFMAX(AV_SYNC_THRESHOLD_MIN, FFMIN(AV_SYNC_THRESHOLD_MAX, delay));
+    if (!isnan(diff))
+    {
+        if (diff <= -sync_threshold)        // 视频时钟落后于同步时钟,且超过同步域值
+            delay = FFMAX(0, delay + diff); // 当前帧播放时刻落后于同步时钟(delay+diff<0)则delay=0(视频追赶,立即播放),否则delay=delay+diff
+        else if (diff >= sync_threshold && delay > AV_SYNC_FRAMEDUP_THRESHOLD)  // 视频时钟超前于同步时钟,且超过同步域值,但上一帧播放时长超长
+            delay = delay + diff;           // 仅仅校正为delay=delay+diff,主要是AV_SYNC_FRAMEDUP_THRESHOLD参数的作用
+        else if (diff >= sync_threshold)    // 视频时钟超前于同步时钟,且超过同步域值
+            delay = 2 * delay;              // 视频播放要放慢脚步,delay扩大至2倍
+    }
+
+	//is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "video: delay=%0.3f A-V=%f", delay, -diff);
+    return delay;
+}
+
+static double vp_duration(player_stat_t *is, frame_t *vp, frame_t *nextvp) 
+{
+    if (vp->serial == nextvp->serial){
+        double duration = nextvp->pts - vp->pts;
+        if (isnan(duration) || duration <= 0)
+            return vp->duration;
+        else
+            return duration;
+    } 
+	else {
+        return 0.0;
+    }
+}
+
+static void update_video_pts(player_stat_t *is, double pts, int64_t pos, int serial) {
+    /* update current video pts */
+    set_clock(&is->video_clk, pts, serial);            // 更新vidclock
+    //-sync_clock_to_slave(&is->extclk, &is->vidclk);  // 将extclock同步到vidclock
+}
+
+static void video_display(player_stat_t *is)
+{
+    frame_t *vp = NULL;
+
+	//is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "%s:%d", __FUNCTION__, __LINE__);
+
+    vp = frame_queue_peek_last(&is->video_frm_queue);
+	if (0 == vp->frame->height || 0 == vp->frame->width){
+		return;
+	}
+
+    // 图像转换:p_frm_raw->data ==> p_frm_yuv->data
+    // 将源图像中一片连续的区域经过处理后更新到目标图像对应区域,处理的图像区域必须逐行连续
+    // plane: 如YUV有Y、U、V三个plane,RGB有R、G、B三个plane
+    // slice: 图像中一片连续的行,必须是连续的,顺序由顶部到底部或由底部到顶部
+    // stride/pitch: 一行图像所占的字节数,Stride=BytesPerPixel*Width+Padding,注意对齐
+    // AVFrame.*data[]: 每个数组元素指向对应plane
+    // AVFrame.linesize[]: 每个数组元素表示对应plane中一行图像所占的字节数
+    sws_scale(is->img_convert_ctx[is->icurrent_index],                    // sws context
+              (const uint8_t *const *)vp->frame->data,			 // src slice
+              vp->frame->linesize,								 // src stride
+              0,												 // src slice y
+              is->p_vcodec_ctx[is->icurrent_index]->height,               // src slice height
+              is->p_frm_yuv[is->icurrent_index]->data,                    // dst planes
+              is->p_frm_yuv[is->icurrent_index]->linesize                 // dst strides
+             );
+
+	//is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "before SDL_WaitEvent %s:%d", __FUNCTION__, __LINE__);
+	//SDL_Event rvcevent;
+	//SDL_WaitEvent(&rvcevent);
+	//is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "after SDL_WaitEvent %s:%d", __FUNCTION__, __LINE__);
+
+	//if(REFRESH_EVENT == rvcevent.type){
+		//SDL_ShowWindow(is->sdl_video.window);
+		// 使用新的YUV像素数据更新SDL_Rect
+		SDL_UpdateYUVTexture(is->sdl_video.texture,								// sdl texture
+			                 &is->sdl_video.rect,								// sdl rect
+				             is->p_frm_yuv[is->icurrent_index]->data[0],        // y plane
+					         is->p_frm_yuv[is->icurrent_index]->linesize[0],    // y pitch
+						     is->p_frm_yuv[is->icurrent_index]->data[1],        // u plane
+							 is->p_frm_yuv[is->icurrent_index]->linesize[1],    // u pitch
+							 is->p_frm_yuv[is->icurrent_index]->data[2],        // v plane
+							 is->p_frm_yuv[is->icurrent_index]->linesize[2]     // v pitch
+							);
+    
+		//is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "SDL_UpdateYUVTexture %s:%d", __FUNCTION__, __LINE__);
+		// 使用特定颜色清空当前渲染目标
+		SDL_RenderClear(is->sdl_video.renderer);
+		//is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "SDL_RenderClear %s:%d", __FUNCTION__, __LINE__);
+		// 使用部分图像数据(texture)更新当前渲染目标
+		SDL_RenderCopy(is->sdl_video.renderer,              // sdl renderer
+			           is->sdl_video.texture,               // sdl texture
+				       NULL,                                // src rect, if NULL copy texture
+					   &is->sdl_video.rect                  // dst rect
+					);
+    
+		//is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "SDL_RenderCopy %s:%d", __FUNCTION__, __LINE__);
+
+		// 执行渲染,更新屏幕显示
+		SDL_RenderPresent(is->sdl_video.renderer);
+		//is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "SDL_RenderPresent %s:%d", __FUNCTION__, __LINE__);
+		SDL_Delay(1);
+	//}
+
+	//is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "%s:%d", __FUNCTION__, __LINE__);
+}
+
+/* called to display each frame */
+static void video_refresh(void *opaque, double *remaining_time)
+{
+    player_stat_t *is = (player_stat_t *)opaque;
+    double time;
+    static bool first_frame = true;
+
+retry:
+    if (frame_queue_nb_remaining(&is->video_frm_queue) == 0)  // 所有帧已显示
+    {    
+        // nothing to do, no picture to display in the queue
+		//is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "%s:%d, nothing to do, no picture to display in the queue.", __FUNCTION__, __LINE__);
+		av_usleep(100*1000);
+		return;
+    }
+
+    double last_duration, duration, delay;
+    frame_t *vp, *lastvp;
+    /* dequeue the picture */
+    lastvp = frame_queue_peek_last(&is->video_frm_queue);     // 上一帧:上次已显示的帧
+    vp = frame_queue_peek(&is->video_frm_queue);              // 当前帧:当前待显示的帧
+    // lastvp和vp不是同一播放序列(一个seek会开始一个新播放序列),将frame_timer更新为当前时间
+    if (first_frame){
+        is->frame_timer = av_gettime_relative() / 1000000.0;
+        first_frame = false;
+    }
+
+    // 暂停处理:不停播放上一帧图像
+    if (is->paused){
+        goto display;
+	}
+
+    /* compute nominal last_duration */
+    last_duration = vp_duration(is, lastvp, vp);        // 上一帧播放时长:vp->pts - lastvp->pts
+    delay = compute_target_delay(last_duration, is);    // 根据视频时钟和同步时钟的差值,计算delay值
+
+    time= av_gettime_relative()/1000000.0;
+    // 当前帧播放时刻(is->frame_timer+delay)大于当前时刻(time),表示播放时刻未到
+    if (time < is->frame_timer + delay) {
+        // 播放时刻未到,则更新刷新时间remaining_time为当前时刻到下一播放时刻的时间差
+        *remaining_time = FFMIN(is->frame_timer + delay - time, *remaining_time);
+        // 播放时刻未到,则不播放,直接返回
+		//is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "%s:%d", __FUNCTION__, __LINE__);
+        return;
+    }
+    // 更新frame_timer值
+    is->frame_timer += delay;
+    // 校正frame_timer值:若frame_timer落后于当前系统时间太久(超过最大同步域值),则更新为当前系统时间
+    if (delay > 0 && time - is->frame_timer > AV_SYNC_THRESHOLD_MAX){
+        is->frame_timer = time;
+    }
+
+    SDL_LockMutex(is->video_frm_queue.frame_mutex);
+
+    if (!isnan(vp->pts)){
+        update_video_pts(is, vp->pts, vp->pos, vp->serial); // 更新视频时钟:时间戳、时钟时间
+    }
+
+    SDL_UnlockMutex(is->video_frm_queue.frame_mutex);
+
+    // 是否要丢弃未能及时播放的视频帧
+    if (frame_queue_nb_remaining(&is->video_frm_queue) > 1)  // 队列中未显示帧数>1(只有一帧则不考虑丢帧)
+    {         
+        frame_t *nextvp = frame_queue_peek_next(&is->video_frm_queue);  // 下一帧:下一待显示的帧
+        duration = vp_duration(is, vp, nextvp);             // 当前帧vp播放时长 = nextvp->pts - vp->pts
+        // 当前帧vp未能及时播放,即下一帧播放时刻(is->frame_timer+duration)小于当前系统时刻(time)
+        if (time > is->frame_timer + duration){
+            frame_queue_next(&is->video_frm_queue);   // 删除上一帧已显示帧,即删除lastvp,读指针加1(从lastvp更新到vp)
+			//is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "%s:%d", __FUNCTION__, __LINE__);
+            goto retry;
+        }
+    }
+
+    // 删除当前读指针元素,读指针+1。若未丢帧,读指针从lastvp更新到vp;若有丢帧,读指针从vp更新到nextvp
+    frame_queue_next(&is->video_frm_queue);
+
+display:
+	//SDL_Event rvcevent;
+	//rvcevent.type = REFRESH_EVENT;
+	//SDL_PushEvent(&rvcevent);
+
+    video_display(is);                      // 取出当前帧vp(若有丢帧是nextvp)进行播放
+}
+
+
+static uint32_t get_video_playing_wind_flag(eWindType_t eType)
+{
+	uint32_t uFlag = SDL_WINDOW_BORDERLESS | SDL_WINDOW_OPENGL | SDL_WINDOW_ALWAYS_ON_TOP | SDL_WINDOW_SKIP_TASKBAR | SDL_WINDOW_POPUP_MENU | SDL_WINDOW_SHOWN;
+
+	return uFlag;
+}
+
+
+static int video_playing_thread(void *arg)
+{
+    player_stat_t *is = (player_stat_t *)arg;
+    double remaining_time = 0.0;
+
+	uint32_t uWindFlag = get_video_playing_wind_flag(is->eWindType);
+
+	is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "%s:%d %d %d %d %d", __FUNCTION__, __LINE__, 
+		is->iDisplayCx,
+		is->iDisplayCy,
+		is->sdl_video.rect.w,
+		is->sdl_video.rect.h);
+
+	// 1. 创建SDL窗口,SDL 2.0支持多窗口
+	//    SDL_Window即运行程序后弹出的视频窗口,同SDL 1.x中的SDL_Surface
+	is->sdl_video.window = SDL_CreateWindow("player",
+		is->iDisplayCx,
+		is->iDisplayCy,
+		is->sdl_video.rect.w,
+		is->sdl_video.rect.h,
+		uWindFlag
+		);
+
+	if (is->sdl_video.window == NULL) {
+		is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "SDL_CreateWindow() failed: %s.", SDL_GetError());
+		return -1;
+	}
+	else {
+		is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "SDL Create Window success.");
+		//SDL_HideWindow(is->sdl_video.window);
+		SDL_SysWMinfo info;
+		HWND hwnd;
+		SDL_VERSION(&info.version);
+		if (SDL_GetWindowWMInfo(is->sdl_video.window, &info)) {
+			hwnd = info.info.win.window;
+			is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "SDL_GetWindowWMInfo success.");
+			SetWindowPos(hwnd,
+				HWND_TOPMOST,
+				is->iDisplayCx,
+				is->iDisplayCy,
+				is->sdl_video.rect.w,
+				is->sdl_video.rect.h,
+				SWP_NOMOVE | SWP_NOSIZE);
+		}
+		else {
+			is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "SDL_GetWindowWMInfo failed.");
+		}
+	}
+
+	//if (eFullScreen_Type == is->eWindType) {
+	//	SDL_SetWindowFullscreen(is->sdl_video.window, SDL_WINDOW_FULLSCREEN_DESKTOP);
+	//}
+
+	int cx = 0, cy = 0;
+	SDL_GetWindowPosition(is->sdl_video.window, &cx, &cy);
+	is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "window flag is 0x%08x, cx = %d, cy = %d.", SDL_GetWindowFlags(is->sdl_video.window), cx, cy);
+
+	// 2. 创建SDL_Renderer
+	//    SDL_Renderer:渲染器
+	int iNum = SDL_GetNumRenderDrivers();
+	is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "SDL_GetNumRenderDrivers %d.", iNum);
+	for (int index = 0; index < iNum; index++){
+		SDL_RendererInfo info = {0};
+		SDL_GetRenderDriverInfo(index, &info);
+		is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "%d render driver name is %s.", index, info.name);
+	}
+
+	//SDL_RendererFlags
+	is->sdl_video.renderer = SDL_CreateRenderer(is->sdl_video.window, -1, 0);
+	if (NULL == is->sdl_video.renderer){
+		is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "SDL_CreateRenderer() failed: %s", SDL_GetError());
+		return -1;
+	}
+
+	// 3. 创建SDL_Texture
+	//    一个SDL_Texture对应一帧YUV数据,同SDL 1.x中的SDL_Overlay
+	is->sdl_video.texture = SDL_CreateTexture(is->sdl_video.renderer,
+		SDL_PIXELFORMAT_IYUV,
+		SDL_TEXTUREACCESS_STATIC,
+		is->sdl_video.rect.w,
+		is->sdl_video.rect.h
+		);
+
+	if (NULL == is->sdl_video.texture){
+		is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "SDL_CreateTexture() failed: %s", SDL_GetError());
+		return -1;
+	}
+
+	SDL_ShowWindow(is->sdl_video.window);
+
+	SDL_Surface* screensurface = SDL_GetWindowSurface(is->sdl_video.window);
+
+	//is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "%s:%d.", __FUNCTION__, __LINE__);
+
+    while ((false == is->buser_stop) && (false == is->bvideo_decode_finished)){
+        if (remaining_time > 0.0){
+            av_usleep((unsigned)(remaining_time * 1000000.0));
+        }
+        remaining_time = REFRESH_RATE;
+
+		//is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "%s:%d.", __FUNCTION__, __LINE__);
+        // 立即显示当前帧,或延时remaining_time后再显示
+        video_refresh(is, &remaining_time);
+		//remaining_time += 0.020;
+		//is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "%s:%d, remaining_time = %f.", __FUNCTION__, __LINE__, remaining_time);
+
+		SDL_Event event;
+		while(SDL_PollEvent(&event))
+		{
+			switch(event.type)
+			{
+			case SDL_QUIT:
+				break;
+			}
+		}
+    }
+
+	is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "video playing thread exit, thread id is %u, and user_stop flag is %s.", SDL_ThreadID(), is->buser_stop ? "true" : "false");
+   
+	return 0;
+}
+
+static int open_video_playing(void* arg)
+{
+	player_stat_t* is = (player_stat_t*)arg;
+	int ret = -1;
+	int buf_size = 0;
+	uint8_t* buffer = NULL;
+	//SDL_Surface* IconSurface = NULL;
+
+	for (size_t index = 0; index < is->uFilesCount; index++) {
+		is->p_frm_yuv[index] = av_frame_alloc();
+		if (NULL == is->p_frm_yuv[index]) {
+			is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "av_frame_alloc() for p_frm_raw failed");
+			return -1;
+		}
+
+		int iplay_video_width = 0;
+		if (NULL != is->p_vcodec_ctx[index]){
+			iplay_video_width = is->p_vcodec_ctx[index]->width;
+		}
+		
+		int iplay_video_height = 0;
+		if (NULL != is->p_vcodec_ctx[index]){
+			iplay_video_height = is->p_vcodec_ctx[index]->height;
+		}
+		
+		if (eFullScreen_Type == is->eWindType || eSpecified_Type == is->eWindType) {
+			iplay_video_width = is->iDisplayWidth;
+			iplay_video_height = is->iDisplayHeight;
+		}
+
+		// 为AVFrame.*data[]手工分配缓冲区,用于存储sws_scale()中目的帧视频数据
+		buf_size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P,
+			iplay_video_width,
+			iplay_video_height,
+			1
+		);
+		is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "av_image_get_buffer_size is %d.", buf_size);
+		// buffer将作为p_frm_yuv的视频数据缓冲区
+		buffer = (uint8_t*)av_malloc(buf_size);
+		if (NULL == buffer) {
+			is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "av_malloc() for buffer failed!");
+			return -1;
+		}
+
+		is->p_video_buffer[index] = buffer;
+		// 使用给定参数设定p_frm_yuv->data和p_frm_yuv->linesize
+		ret = av_image_fill_arrays(is->p_frm_yuv[index]->data,     // dst data[]
+			is->p_frm_yuv[index]->linesize,						   // dst linesize[]
+			is->p_video_buffer[index],							   // src buffer
+			AV_PIX_FMT_YUV420P,									   // pixel format
+			iplay_video_width,									   // width
+			iplay_video_height,									   // height
+			1													   // align
+		);
+
+		if (ret < 0) {
+			is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "av_image_fill_arrays() failed %d", ret);
+			return -1;;
+		}
+
+		// A2. 初始化SWS context,用于后续图像转换
+		//     此处第6个参数使用的是FFmpeg中的像素格式,对比参考注释B3
+		//     FFmpeg中的像素格式AV_PIX_FMT_YUV420P对应SDL中的像素格式SDL_PIXELFORMAT_IYUV
+		//     如果解码后得到图像的不被SDL支持,不进行图像转换的话,SDL是无法正常显示图像的
+		//     如果解码后得到图像的能被SDL支持,则不必进行图像转换
+		//     这里为了编码简便,统一转换为SDL支持的格式AV_PIX_FMT_YUV420P==>SDL_PIXELFORMAT_IYUV
+		is->img_convert_ctx[index] = sws_getContext(is->p_vcodec_ctx[index]->width,   // src width
+			is->p_vcodec_ctx[index]->height,  // src height
+			is->p_vcodec_ctx[index]->pix_fmt, // src format
+			iplay_video_width,			// dst width
+			iplay_video_height,			// dst height
+			AV_PIX_FMT_YUV420P,        // dst format
+			SWS_BICUBIC,               // flags
+			NULL,                      // src filter
+			NULL,                      // dst filter
+			NULL                       // param
+		);
+
+		if (NULL == is->img_convert_ctx[index]) {
+			is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "sws_getContext() failed.");
+			return -1;
+		}
+		else {
+			is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "%s:%d is->img_convert_ctx[%d] = 0x%08x.", __FUNCTION__, __LINE__, index, is->img_convert_ctx[index]);
+		}
+
+		// SDL_Rect赋值
+		is->sdl_video.rect.x = 0;
+		is->sdl_video.rect.y = 0;
+		is->sdl_video.rect.w = iplay_video_width;
+		is->sdl_video.rect.h = iplay_video_height;
+
+		//uint32_t uWindFlag = get_video_playing_wind_flag(is->eWindType);
+
+		//is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "%s:%d %d %d %d %d", __FUNCTION__, __LINE__, 
+		//is->iDisplayCx,
+		//is->iDisplayCy,
+		//is->sdl_video.rect.w,
+		//is->sdl_video.rect.h);
+
+		//// 1. 创建SDL窗口,SDL 2.0支持多窗口
+		////    SDL_Window即运行程序后弹出的视频窗口,同SDL 1.x中的SDL_Surface
+		//is->sdl_video.window = SDL_CreateWindow("player",
+		//	is->iDisplayCx,
+		//	is->iDisplayCy,
+		//	is->sdl_video.rect.w,
+		//	is->sdl_video.rect.h,
+		//	uWindFlag
+		//);
+
+		//if (is->sdl_video.window == NULL) {
+		//	is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "SDL_CreateWindow() failed: %s.", SDL_GetError());
+		//	return -1;
+		//}
+		//else {
+		//	is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "SDL Create Window success.");
+		//	SDL_HideWindow(is->sdl_video.window);
+
+		//	//SDL_Surface* screensurface = SDL_GetWindowSurface(is->sdl_video.window);
+		//	//SDL_FillRect(screensurface, NULL, SDL_MapRGB(screensurface->format, 0xFF, 0xFF, 0xFF));
+		//	//SDL_UpdateWindowSurface(is->sdl_video.window);
+		//}
+
+		//if (eFullScreen_Type == is->eWindType) {
+		//	SDL_SetWindowFullscreen(is->sdl_video.window, SDL_WINDOW_FULLSCREEN_DESKTOP);
+		//}
+
+		//int cx = 0, cy = 0;
+		//SDL_GetWindowPosition(is->sdl_video.window, &cx, &cy);
+		//is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "window flag is 0x%08x, cx = %d, cy = %d.", SDL_GetWindowFlags(is->sdl_video.window), cx, cy);
+
+		////if (NULL != is->piconpath) {
+		////	IconSurface = SDL_LoadBMP(is->piconpath);
+		////	if (NULL == IconSurface) {
+		////		is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "SDL_LoadBMP(%s) failed: %s", is->piconpath, SDL_GetError());
+		////	}
+		////	else {
+		////		SDL_SetWindowIcon(is->sdl_video.window, IconSurface);
+		////		SDL_FreeSurface(IconSurface);
+		////	}
+		////}
+
+		//// 2. 创建SDL_Renderer
+		////    SDL_Renderer:渲染器
+		//int iNum = SDL_GetNumRenderDrivers();
+		//is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "SDL_GetNumRenderDrivers %d.", iNum);
+		//for (int index = 0; index < iNum; index++){
+		//	SDL_RendererInfo info = {0};
+		//	SDL_GetRenderDriverInfo(index, &info);
+		//	is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "%d render driver name is %s.", index, info.name);
+		//}
+
+		////SDL_RendererFlags
+		//is->sdl_video.renderer = SDL_CreateRenderer(is->sdl_video.window, -1, 0);
+		//if (NULL == is->sdl_video.renderer){
+		//	is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "SDL_CreateRenderer() failed: %s", SDL_GetError());
+		//	return -1;
+		//}
+
+		//// 3. 创建SDL_Texture
+		////    一个SDL_Texture对应一帧YUV数据,同SDL 1.x中的SDL_Overlay
+		//is->sdl_video.texture = SDL_CreateTexture(is->sdl_video.renderer,
+		//	SDL_PIXELFORMAT_IYUV,
+		//	SDL_TEXTUREACCESS_STATIC,
+		//	is->sdl_video.rect.w,
+		//	is->sdl_video.rect.h
+		//);
+
+		//if (NULL == is->sdl_video.texture){
+		//	is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "SDL_CreateTexture() failed: %s", SDL_GetError());
+		//	return -1;
+		//}
+
+		//SDL_ShowWindow(is->sdl_video.window);
+
+		is->video_playing_tid = SDL_CreateThread(video_playing_thread, "video playing thread", is);
+		if (NULL == is->video_playing_tid) {
+			is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "SDL_Create video playing thread failed: %s.", SDL_GetError());
+			return -1;
+		}
+		else {
+			is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "create %s success, and thread id is %u.", SDL_GetThreadName(is->video_playing_tid), SDL_GetThreadID(is->video_playing_tid));
+		}
+	}
+
+    return 0;
+}
+
+static int open_video_stream(player_stat_t *is)
+{
+    AVCodecParameters* p_codec_par = NULL;
+    AVCodec* p_codec = NULL;
+    AVCodecContext* p_codec_ctx = NULL;
+	int ret = -1;
+
+	for (size_t index = 0; index < is->uFilesCount; index++){
+		AVStream* p_stream = is->p_video_stream[index];
+
+		// 1. 为视频流构建解码器AVCodecContext
+		// 1.1 获取解码器参数AVCodecParameters
+		p_codec_par = p_stream->codecpar;
+
+		// 1.2 获取解码器
+		p_codec = avcodec_find_decoder(p_codec_par->codec_id);
+		if (p_codec == NULL) {
+			is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "can not find codec!");
+			return ret;
+		}
+
+		// 1.3 构建解码器AVCodecContext
+		// 1.3.1 p_codec_ctx初始化:分配结构体,使用p_codec初始化相应成员为默认值
+		p_codec_ctx = avcodec_alloc_context3(p_codec);
+		if (p_codec_ctx == NULL) {
+			is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "avcodec_alloc_context3() failed");
+			return ret;
+		}
+		// 1.3.2 p_codec_ctx初始化:p_codec_par ==> p_codec_ctx,初始化相应成员
+		ret = avcodec_parameters_to_context(p_codec_ctx, p_codec_par);
+		if (ret < 0) {
+			is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "avcodec_parameters_to_context() failed");
+			return ret;
+		}
+		// 1.3.3 p_codec_ctx初始化:使用p_codec初始化p_codec_ctx,初始化完成
+		ret = avcodec_open2(p_codec_ctx, p_codec, NULL);
+		if (ret < 0) {
+			is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "avcodec_open2() failed %d", ret);
+			return ret;
+		}
+		is->p_vcodec_ctx[index] = p_codec_ctx;
+	}
+
+    // 2. 创建视频解码线程
+	is->video_decode_tid = SDL_CreateThread(video_decode_thread, "video decode thread", is);
+	if (NULL == is->video_decode_tid) {
+		is->rvc_hostapi->Debug(MEDIA_LOG_ERROR, "SDL_Create video decode thread failed: %s.", SDL_GetError());
+		return -1;
+	}
+	else {
+		is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "create %s success, and thread id is %u.", SDL_GetThreadName(is->video_decode_tid), SDL_GetThreadID(is->video_decode_tid));
+	}
+
+    return 0;
+}
+
+int open_video(player_stat_t *is)
+{
+	is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "enter open_video()");
+    open_video_stream(is);
+    open_video_playing(is);
+	is->rvc_hostapi->Debug(MEDIA_LOG_DEBUG, "exit open_video()");
+
+    return 0;
+}

+ 7 - 0
Other/win/libmediaplayer/video.h

@@ -0,0 +1,7 @@
+#pragma once
+
+#include "player.h"
+
+int open_video(player_stat_t *is);
+
+