#include #include #include "pl_mpeg.h" #include "miniaudio.h" #include "stb_ds.h" #define MULTIPLE_BUFFER #define PERIOD PLM_AUDIO_SAMPLES_PER_FRAME #define BUFFERS 3 typedef struct buffer { float buffer[PERIOD * 2]; } buffer_t; static ma_device device; static buffer_t buffers[BUFFERS]; static int buf_rseek = 0; static int buf_wseek = 0; static int buf_rframe = 0; static int buf_wframe = 0; static ma_mutex buf_mutex; static MwWidget box, opengl, progress; static plm_t* plm = NULL; static GLuint tex = 0; static unsigned char* vid = NULL; static int vw, vh; static int ww, wh; static double last; static int progress_move = 1; static int paused = 0; static int sample_rate = 0; static void camera(int w, int h){ MwOpenGLMakeCurrent(opengl); glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0, w, h, 0, -1, 1); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } static void MWAPI resize(MwWidget widget, void* user, void* call){ ww = MwGetInteger(widget, MwNwidth); wh = MwGetInteger(widget, MwNheight); MwVaApply(box, MwNwidth, ww, MwNheight, wh, NULL); } static void MWAPI opengl_resize(MwWidget widget, void* user, void* call){ camera(MwGetInteger(widget, MwNwidth), MwGetInteger(widget, MwNheight)); } static void MWAPI progress_mouseDown(MwWidget widget, void* user, void* call){ MwMouse* m = call; if(m->button == MwMOUSE_LEFT) progress_move = 0; } static void MWAPI progress_mouseUp(MwWidget widget, void* user, void* call){ MwMouse* m = call; if(m->button == MwMOUSE_LEFT) progress_move = 1; plm_seek(plm, (double)MwGetInteger(widget, MwNvalue) / 1000, 1); } static void MWAPI play_activate(MwWidget widget, void* user, void* call){ plm_rewind(plm); } static void MWAPI pause_activate(MwWidget widget, void* user, void* call){ paused = !paused; } static void on_audio(plm_t *mpeg, plm_samples_t *samples, void *user) { buffer_t buf; int i; int total; total = samples->count; for(i = 0; i < total; i++){ int b = i * 2; buf.buffer[i * 2 + 0] = samples->interleaved[b + 0]; buf.buffer[i * 2 + 1] = samples->interleaved[b + 1]; } ma_mutex_lock(&buf_mutex); buffers[buf_wseek++] = buf; if(buf_wseek == BUFFERS) buf_wseek = 0; buf_wframe++; ma_mutex_unlock(&buf_mutex); } static void on_video(plm_t* mpeg, plm_frame_t* frame, void* user){ plm_frame_to_rgb(frame, vid, vw * 3); MwOpenGLMakeCurrent(opengl); glBindTexture(GL_TEXTURE_2D, tex); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, vw, vh, GL_RGB, GL_UNSIGNED_BYTE, vid); glBindTexture(GL_TEXTURE_2D, 0); } static void MWAPI tick(MwWidget widget, void* user, void* call){ double sx = 0; double sy = 0; double sw = 0; double sh = 0; if(((double)ww / vw) < ((double)wh / vh)){ sw = ww; sh = (double)ww / vw * vh; }else{ sh = wh; sw = (double)wh / vh * vw; } sx = ((double)MwGetInteger(opengl, MwNwidth) - sw) / 2; sy = ((double)MwGetInteger(opengl, MwNheight) - sh) / 2; MwOpenGLMakeCurrent(opengl); glClear(GL_COLOR_BUFFER_BIT); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, tex); glColor3f(1, 1, 1); glBegin(GL_QUADS); glTexCoord2f(0, 0); glVertex2f(sx, sy); glTexCoord2f(0, 1); glVertex2f(sx, sy + sh); glTexCoord2f(1, 1); glVertex2f(sx + sw, sy + sh); glTexCoord2f(1, 0); glVertex2f(sx + sw, sy); glEnd(); glBindTexture(GL_TEXTURE_2D, 0); glDisable(GL_TEXTURE_2D); MwOpenGLSwapBuffer(opengl); } static int load(const char* input){ int i; buf_wframe = buf_wseek = 0; memset(&buffers, 0, sizeof(buffers)); if(plm != NULL) plm_destroy(plm); if((plm = plm_create_with_filename(input)) == NULL || !plm_probe(plm, 5000 * 1024)){ if(plm != NULL){ plm_destroy(plm); plm = NULL; } return 1; } plm_set_audio_decode_callback(plm, on_audio, NULL); plm_set_video_decode_callback(plm, on_video, NULL); plm_set_loop(plm, 1); sample_rate = plm_get_samplerate(plm); MwOpenGLMakeCurrent(opengl); vw = plm_get_width(plm); vh = plm_get_height(plm); MwVaApply(progress, MwNmaxValue, (int)(plm_get_duration(plm) * 1000), MwNvalue, 0, MwNareaShown, 1000 * 10, NULL); if(vid != NULL) free(vid); vid = malloc(vw * vh * 3); memset(vid, 0, vw * vh * 3); for(i = 0; i < vw * vh; i++){ vid[i * 3 + 0] = i / vw * 255 / vh; vid[i * 3 + 2] = (i % vw) * 255 / vw; } if(tex != 0) glDeleteTextures(1, &tex); glGenTextures(1, &tex); glBindTexture(GL_TEXTURE_2D, tex); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, vw, vh, 0, GL_RGB, GL_UNSIGNED_BYTE, vid); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glBindTexture(GL_TEXTURE_2D, 0); last = (double)MwTimeGetTick() / 1000; return 0; } static void data_callback(ma_device* device, void* output, const void* input, ma_uint32 frame){ short* o = output; int i; memset(o, 0, frame * sizeof(short) * 2); ma_mutex_lock(&buf_mutex); for(i = 0; i < frame * 2; i++){ o[i] = buffers[buf_rseek].buffer[i] * 32767; } ma_mutex_unlock(&buf_mutex); buf_rseek++; if(buf_rseek == BUFFERS) buf_rseek = 0; buf_rframe++; if(buf_rframe > buf_wframe){ buf_rframe = buf_wframe; buf_rseek = buf_wseek; } } static int init(void){ ma_device_config config; ma_mutex_init(&buf_mutex); config = ma_device_config_init(ma_device_type_playback); config.playback.format = ma_format_s16; config.playback.channels = 2; config.sampleRate = 48000; config.dataCallback = data_callback; config.pUserData = NULL; config.periodSizeInFrames = PERIOD; if(ma_device_init(NULL, &config, &device) != MA_SUCCESS){ return 1; } if(ma_device_start(&device) != MA_SUCCESS){ ma_device_uninit(&device); return 1; } MwOpenGLMakeCurrent(opengl); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); return 0; } int main(int argc, char** argv){ MwWidget w, bar, play, pause; int st; MwLibraryInit(); w = MwVaCreateWidget(MwWindowClass, "main", NULL, MwDEFAULT, MwDEFAULT, 800, 630, MwNtitle, "poc player", NULL); box = MwVaCreateWidget(MwBoxClass, "box", w, 0, 0, 0, 0, MwNorientation, MwVERTICAL, NULL); opengl = MwCreateWidget(MwOpenGLClass, "opengl", box, 0, 0, 0, 0); bar = MwVaCreateWidget(MwBoxClass, "bar", box, 0, 0, 0, 0, MwNfixedSize, 30, MwNorientation, MwHORIZONTAL, NULL); play = MwVaCreateWidget(MwButtonClass, "play", bar, 0, 0, 0, 0, MwNtext, "Play", MwNfixedSize, 60, NULL); pause = MwVaCreateWidget(MwButtonClass, "pause", bar, 0, 0, 0, 0, MwNtext, "Pause", MwNfixedSize, 60, NULL); progress = MwVaCreateWidget(MwScrollBarClass, "progress", bar, 0, 0, 0, 0, MwNshowArrows, 0, MwNorientation, MwHORIZONTAL, NULL); MwAddUserHandler(w, MwNresizeHandler, resize, NULL); MwAddUserHandler(w, MwNtickHandler, tick, NULL); MwAddUserHandler(opengl, MwNresizeHandler, opengl_resize, NULL); MwAddUserHandler(progress, MwNmouseDownHandler, progress_mouseDown, NULL); MwAddUserHandler(progress, MwNmouseUpHandler, progress_mouseUp, NULL); MwAddUserHandler(play, MwNactivateHandler, play_activate, NULL); MwAddUserHandler(pause, MwNactivateHandler, pause_activate, NULL); if((st = init()) != 0) return st; resize(w, NULL, NULL); if((st = load(argv[1])) != 0) return st; while(1){ double l2, elapsed; while(MwPending(w)) MwStep(w); elapsed = (l2 = (double)MwTimeGetTick() / 1000) - last; if(elapsed > 1.0 / 30) elapsed = 1.0 / 30; if(paused){ buffer_t buf; memset(&buf, 0, sizeof(buf)); ma_mutex_lock(&buf_mutex); buffers[buf_wseek] = buf; if(buf_wseek == BUFFERS) buf_wseek = 0; ma_mutex_unlock(&buf_mutex); }else{ plm_decode(plm, elapsed); last = l2; if(progress_move){ MwVaApply(progress, MwNvalue, (int)(plm_get_time(plm) * 1000), NULL); } } } }