/* * Copyright (c) 2010-2013 Hypertriton, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Playback widget. */ #include #include #include #include int vsPlayerLOD = 0; /* Auto LOD adjustment (for slow hw) */ int vsPlayerButtonHeight = 20; VS_Player * VS_PlayerNew(void *parent, Uint flags, struct vs_clip *clip) { VS_Player *vp; VS_Project *vsp = clip->proj; vp = Malloc(sizeof(VS_Player)); AG_ObjectInit(vp, &vsPlayerClass); vp->flags |= flags; vp->clip = clip; AG_BindFlag(vp->btn[VS_PLAYER_PLAY], "state", &vsp->flags, VS_PROJECT_PLAYING); AG_BindFlag(vp->btn[VS_PLAYER_REC], "state", &vsp->flags, VS_PROJECT_RECORDING); if (flags & VS_PLAYER_HFILL) { AG_ExpandHoriz(vp); } if (flags & VS_PLAYER_VFILL) { AG_ExpandVert(vp); } AG_ObjectAttach(parent, vp); return (vp); } void VS_PlayerSizeHint(VS_Player *vp, Uint w, Uint h) { vp->wPre = w; vp->hPre = h; } static void Rewind(AG_Event *event) { VS_Player *vp = AG_PTR(1); AG_ObjectLock(vp); AG_ObjectLock(vp->clip->proj); vp->clip->x = 0; AG_ObjectUnlock(vp->clip->proj); AG_ObjectUnlock(vp); } static void Forward(AG_Event *event) { VS_Player *vp = AG_PTR(1); VS_Project *vsp; AG_ObjectLock(vp); vsp = vp->clip->proj; AG_ObjectLock(vsp); if (vp->clip->n > 0) vp->clip->x = (vp->clip->n - 1); AG_ObjectUnlock(vsp); AG_ObjectUnlock(vp); } void VS_Stop(VS_Player *vp) { VS_Clip *v; VS_Project *vsp; AG_ObjectLock(vp); v = vp->clip; vsp = v->proj; AG_ObjectLock(vsp); if (v->sndBuf != NULL) { if (VS_StopAudio(vp) == -1) { VS_Status(vsp, _("Failed to stop audio: %s"), AG_GetError()); } else { VS_Status(vsp, _("Audio/video playback stopped")); } } else { VS_Status(vsp, _("Video playback stopped")); } vsp->flags &= ~(VS_PROJECT_RECORDING); vsp->flags &= ~(VS_PROJECT_PLAYING); vp->flags &= ~(VS_PLAYER_PLAYING); AG_ObjectUnlock(vsp); AG_ObjectUnlock(vp); } void VS_Play(VS_Player *vp) { VS_Clip *v; VS_Project *vsp; AG_ObjectLock(vp); v = vp->clip; vsp = v->proj; AG_ObjectLock(vsp); VS_Stop(vsp->gui.playerIn); VS_Stop(vsp->gui.playerOut); vsp->flags &= ~(VS_PROJECT_RECORDING); vsp->flags &= ~(VS_PROJECT_PLAYING); if (v->sndBuf != NULL) { if (VS_PlayAudio(vp) == -1) { VS_Status(vsp, _("Failed to start audio: %s"), AG_GetError()); goto out; } else { VS_Status(vsp, _("Playing (%u frames, %d-Ch, %uHz)"), v->n, v->sndInfo.channels, v->sndInfo.samplerate); } } else { VS_Status(vsp, _("Playing (%u frames, no sound)"), v->n); } vp->flags |= VS_PLAYER_PLAYING; vsp->flags |= VS_PROJECT_PLAYING; out: AG_ObjectUnlock(vsp); AG_ObjectUnlock(vp); } static void Play(AG_Event *event) { VS_Player *vp = AG_PTR(1); VS_Play(vp); } static void Stop(AG_Event *event) { VS_Player *vp = AG_PTR(1); VS_Stop(vp); } static void Record(AG_Event *event) { VS_Player *vp = AG_PTR(1); VS_Project *vsp; AG_ObjectLock(vp); vsp = vp->clip->proj; AG_ObjectLock(vsp); if (vsp->flags & VS_PROJECT_RECORDING) goto out; VS_Stop(vsp->gui.playerIn); VS_Stop(vsp->gui.playerOut); /* TODO allow other tracks to be played */ if (vsp->gui.playerOut->clip->sndBuf != NULL && VS_PlayAudio(vsp->gui.playerOut) == -1) { goto out; } vsp->flags |= VS_PROJECT_RECORDING; out: AG_ObjectUnlock(vsp); AG_ObjectUnlock(vp); } static void Init(void *obj) { VS_Player *vp = obj; int i; vp->clip = NULL; vp->wPre = 320; vp->hPre = 240 + vsPlayerButtonHeight; vp->xLast = -1; vp->suScaled = -1; vp->btn[VS_PLAYER_REW] = AG_ButtonNewFn(vp, 0, _("Rew"), Rewind, "%p", vp); vp->btn[VS_PLAYER_PLAY] = AG_ButtonNewFn(vp, AG_BUTTON_STICKY, _("Play"), Play, "%p", vp); vp->btn[VS_PLAYER_STOP] = AG_ButtonNewFn(vp, 0, _("Stop"), Stop, "%p", vp); vp->btn[VS_PLAYER_FWD] = AG_ButtonNewFn(vp, 0, _("Fwd"), Forward, "%p", vp); vp->btn[VS_PLAYER_REC] = AG_ButtonNewFn(vp, AG_BUTTON_STICKY, _("Rec"), Record, "%p", vp); } static void SizeRequest(void *obj, AG_SizeReq *r) { VS_Player *vp = obj; r->w = vp->wPre; r->h = vp->hPre; } static int SizeAllocate(void *obj, const AG_SizeAlloc *a) { VS_Player *vp = obj; AG_SizeAlloc aBtn; int wBtn = (a->w / VS_PLAYER_LASTBTN) - 1; int i; vp->rVid.x = 0; vp->rVid.y = 0; vp->rVid.w = a->w; vp->rVid.h = MIN(a->w, a->h) - vsPlayerButtonHeight; vp->flags |= VS_PLAYER_REFRESH; aBtn.x = 1; aBtn.y = a->h - vsPlayerButtonHeight; aBtn.w = wBtn; aBtn.h = vsPlayerButtonHeight; for (i = 0; i < VS_PLAYER_LASTBTN; i++) { AG_WidgetSizeAlloc(vp->btn[i], &aBtn); aBtn.x += wBtn + 1; } return (0); } /* * Video rendering */ /* Update video from a frame thumbnail image. */ static void DrawFromThumb(VS_Player *vp, AG_Surface *suSrc) { AG_Surface *suScaled = NULL; /* XXX TODO: interlacing */ if (AG_ScaleSurface(suSrc, vp->rVid.w, vp->rVid.h, &suScaled) == -1) { return; } if (vp->suScaled == -1) { vp->suScaled = AG_WidgetMapSurface(vp, suScaled); } else { AG_WidgetReplaceSurface(vp, vp->suScaled, suScaled); } return; fail: if (vp->suScaled != -1) { AG_WidgetUnmapSurface(vp, vp->suScaled); vp->suScaled = -1; } } /* * Update video from image file. */ struct my_error_mgr { struct jpeg_error_mgr pub; jmp_buf setjmp_buffer; }; typedef struct my_error_mgr *my_error_ptr; METHODDEF(void) my_error_exit(j_common_ptr cinfo) { my_error_ptr myerr = (my_error_ptr)cinfo->err; (*cinfo->err->output_message)(cinfo); longjmp(myerr->setjmp_buffer, 1); } static void DrawFromJPEG(VS_Player *vp, VS_Clip *v, VS_Frame *vf) { char path[AG_PATHNAME_MAX]; FILE *f; struct jpeg_decompress_struct cinfo; struct my_error_mgr jerr; JSAMPROW pRow[1]; AG_Surface *su = NULL, *suScaled; snprintf(path, sizeof(path), v->fileFmt, v->dir, vf->f); if ((f = fopen(path, "r")) == NULL) return; cinfo.err = jpeg_std_error(&jerr.pub); jerr.pub.error_exit = my_error_exit; if (setjmp(jerr.setjmp_buffer)) { jpeg_destroy_decompress(&cinfo); return; } jpeg_create_decompress(&cinfo); jpeg_stdio_src(&cinfo, f); jpeg_read_header(&cinfo, TRUE); /* Allocate Agar surface */ if (cinfo.num_components == 4) { cinfo.out_color_space = JCS_CMYK; cinfo.quantize_colors = FALSE; jpeg_calc_output_dimensions(&cinfo); su = AG_SurfaceRGBA( cinfo.output_width, cinfo.output_height, 32, 0, #if AG_BYTEORDER == AG_BIG_ENDIAN 0x0000FF00, 0x00FF0000, 0xFF000000, 0x000000FF #else 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000 #endif ); } else { cinfo.out_color_space = JCS_RGB; cinfo.quantize_colors = FALSE; #if 1 /* For speed */ cinfo.scale_num = 1; cinfo.scale_denom = 1; cinfo.dct_method = JDCT_FASTEST; cinfo.do_fancy_upsampling = FALSE; #endif jpeg_calc_output_dimensions(&cinfo); su = AG_SurfaceRGB( cinfo.output_width, cinfo.output_height, 24, 0, #if AG_BYTEORDER == AG_BIG_ENDIAN 0xff0000, 0x00ff00, 0x0000ff #else 0x0000ff, 0x00ff00, 0xff0000 #endif ); } if (su == NULL) goto fail_free; /* Start decompression */ jpeg_start_decompress(&cinfo); while (cinfo.output_scanline < su->h) { pRow[0] = (JSAMPROW)(Uint8 *)su->pixels + cinfo.output_scanline*su->pitch; jpeg_read_scanlines(&cinfo, pRow, (JDIMENSION)1); } /* * Scale to preview size. * XXX TODO: interlacing */ suScaled = NULL; if (AG_ScaleSurface(su, vp->rVid.w, vp->rVid.h, &suScaled) == -1) { goto fail_free; } if (vp->suScaled == -1) { vp->suScaled = AG_WidgetMapSurface(vp, suScaled); } else { AG_WidgetReplaceSurface(vp, vp->suScaled, suScaled); } AG_SurfaceFree(su); jpeg_finish_decompress(&cinfo); jpeg_destroy_decompress(&cinfo); return; fail_free: if (vp->suScaled != -1) { AG_WidgetUnmapSurface(vp, vp->suScaled); vp->suScaled = -1; } if (su != NULL) { AG_SurfaceFree(su); } jpeg_finish_decompress(&cinfo); jpeg_destroy_decompress(&cinfo); } static void Draw(void *obj) { VS_Player *vp = obj; VS_Clip *v = vp->clip; VS_Project *vsp = v->proj; int i; AG_ObjectLock(vsp); if (vsp->procOp != VS_PROC_IDLE || v->x >= v->n) { AG_Color c; AG_ColorBlack(&c); AG_DrawBox(vp, &vp->rVid, -1, &c); goto out; } if (vsPlayerLOD) { if (vp->xLast != v->x || vp->flags & VS_PLAYER_REFRESH) { vp->xLast = v->x; DrawFromThumb(vp, v->frames[v->x].thumb); vp->flags &= ~(VS_PLAYER_REFRESH|VS_PLAYER_LOD); } else { if (!(vp->flags & VS_PLAYER_LOD) && vp->lodTimeout++ > 5) { vp->lodTimeout = 0; vp->flags |= VS_PLAYER_LOD; DrawFromJPEG(vp, v, &v->frames[v->x]); } } } else { DrawFromJPEG(vp, v, &v->frames[v->x]); } AG_PushClipRect(vp, &vp->rVid); if (vp->suScaled != -1) { AG_WidgetBlitSurface(vp, vp->suScaled, 0, 0); } AG_PopClipRect(vp); out: AG_ObjectUnlock(vsp); for (i = 0; i < VS_PLAYER_LASTBTN; i++) AG_WidgetDraw(vp->btn[i]); } /* * Audio Playback */ static int AudioUpdateStereo(const void *pIn, void *pOut, Ulong count, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *pData) { VS_Player *vp = pData; VS_Clip *v = vp->clip; float *out = (float *)pOut; int ch; Ulong i; for (i = 0; i < count; i++) { if (v->sndPos+1 >= v->sndInfo.frames*2) { *out++ = 0; *out++ = 0; continue; } *out++ = v->sndBuf[v->sndPos]; *out++ = v->sndBuf[v->sndPos+1]; v->sndPos+=2; } v->drift = v->sndPos - v->x*v->samplesPerFrame; if (v->drift > v->samplesPerFrame*2 || v->drift < v->samplesPerFrame*2) { v->sndPos = v->x*v->samplesPerFrame; } return (paContinue); } static int AudioUpdateMono(const void *pIn, void *pOut, Ulong count, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *pData) { VS_Player *vp = pData; VS_Clip *v = vp->clip; float *out = (float *)pOut; Ulong i; for (i = 0; i < count; i++) { if (v->sndPos+1 >= v->sndInfo.frames) { *out++ = 0; continue; } *out++ = v->sndBuf[v->sndPos]; v->sndPos++; } v->drift = v->sndPos - v->x*v->samplesPerFrame; if (v->drift > v->samplesPerFrame*2 || v->drift < v->samplesPerFrame*2) { v->sndPos = v->x*v->samplesPerFrame; } return (paContinue); } #if 0 static void VS_PlayerAudioFinishedCallback(void *pData) { VS_Clip *v = pData; printf("audio finished!\n"); } #endif /* Start audio playback */ int VS_PlayAudio(VS_Player *vp) { VS_Clip *v = vp->clip; PaStreamParameters op; PaError rv; AG_MutexLock(&v->sndLock); if (v->sndBuf == NULL) { AG_SetError("Clip has no audio"); goto fail; } if ((op.device = Pa_GetDefaultOutputDevice()) == -1) { AG_SetError("No audio output device"); goto fail; } if (v->sndInfo.channels != 1 && v->sndInfo.channels != 2) { AG_SetError("%d-Ch playback unimplemented", v->sndInfo.channels); goto fail; } op.channelCount = v->sndInfo.channels; op.sampleFormat = paFloat32; op.suggestedLatency = Pa_GetDeviceInfo(op.device)->defaultLowOutputLatency; op.hostApiSpecificStreamInfo = NULL; rv = Pa_OpenStream( &v->sndStream, NULL, &op, v->sndInfo.samplerate, v->samplesPerFrame, /* frames per buffer */ paClipOff, (v->sndInfo.channels == 2) ? AudioUpdateStereo : AudioUpdateMono, vp); if (rv != paNoError) { AG_SetError("Failed to open playback device: %s", Pa_GetErrorText(rv)); goto fail; } #if 0 rv = Pa_SetStreamFinishedCallback(v->sndStream, VS_PlayerAudioFinishedCallback); if (rv != paNoError) goto pafail; #endif rv = Pa_StartStream(v->sndStream); if (rv != paNoError) goto pafail; AG_MutexUnlock(&v->sndLock); return (0); pafail: AG_SetError("PortAudio error: %s", Pa_GetErrorText(rv)); fail: AG_MutexUnlock(&v->sndLock); return (-1); } int VS_StopAudio(VS_Player *vp) { VS_Clip *v = vp->clip; PaError rv; AG_MutexLock(&v->sndLock); if (v->sndStream == NULL) { AG_SetError("Audio is not playing"); goto fail; } if ((rv = Pa_StopStream(v->sndStream)) != paNoError) { AG_SetError("%s", Pa_GetErrorText(rv)); goto fail; } AG_MutexUnlock(&v->sndLock); return (0); fail: AG_MutexUnlock(&v->sndLock); return (0); } AG_WidgetClass vsPlayerClass = { { "AG_Widget:VS_Player", sizeof(VS_Player), { 0,0 }, Init, NULL, /* free */ NULL, /* destroy */ NULL, /* load */ NULL, /* save */ NULL /* edit */ }, Draw, SizeRequest, SizeAllocate };