/** @file * @author Edouard DUPIN * @copyright 2016, Edouard DUPIN, all right reserved * @license GPL v3 (see license file) */ #include #include #include #include #include #include // VBO table property: const int32_t appl::widget::VideoDisplay::m_vboIdCoord(0); const int32_t appl::widget::VideoDisplay::m_vboIdCoordTex(1); const int32_t appl::widget::VideoDisplay::m_vboIdColor(2); #define NB_VBO (3) appl::widget::VideoDisplay::VideoDisplay() : m_light(1.0f) { addObjectType("appl::widget::VideoDisplay"); m_color = etk::color::white; m_nbFramePushed = 0; m_isPalying = false; } void appl::widget::VideoDisplay::init() { ewol::Widget::init(); markToRedraw(); // set call all time (sample ...). getObjectManager().periodicCall.connect(sharedFromThis(), &appl::widget::VideoDisplay::periodicEvent); // Create the VBO: m_VBO = gale::resource::VirtualBufferObject::create(NB_VBO); if (m_VBO == null) { APPL_ERROR("can not instanciate VBO ..."); return; } m_VBO->setName("[VBO] of appl::widget::VideoDisplay"); loadProgram(); m_matrixApply.identity(); // By default we load a graphic resource ... m_useElement = 0; for (int32_t iii=0; iiiget().configure(ivec2(128,128), egami::colorType::RGB8); // Configure to use the minimum of CPU m_resource[iii]->setRepeat(false); m_resource[iii]->setFilterMode(ewol::resource::TextureFilter::nearest); } } // Create the River manager for tha application or part of the application. m_audioManager = audio::river::Manager::create("zeus-video-player"); } appl::widget::VideoDisplay::~VideoDisplay() { } void appl::widget::VideoDisplay::loadProgram() { // get the shader resource: m_GLPosition = 0; m_GLprogram.reset(); m_GLprogram = gale::resource::Program::create("DATA:///textured3D.prog?lib=ewol"); if (m_GLprogram != null) { m_GLPosition = m_GLprogram->getAttribute("EW_coord3d"); m_GLColor = m_GLprogram->getAttribute("EW_color"); m_GLtexture = m_GLprogram->getAttribute("EW_texture2d"); m_GLMatrix = m_GLprogram->getUniform("EW_MatrixTransformation"); m_GLtexID = m_GLprogram->getUniform("EW_texID"); } } void appl::widget::VideoDisplay::setFile(const etk::String& _filename) { /* // Stop playing in all case... stop(); // Clear the old interface m_decoder.reset(); // Create a new interface m_decoder = ememory::makeShared(); if (m_decoder == null) { APPL_ERROR("Can not create sharedPtr on decoder ..."); return; } m_decoder->init(_filename); */ markToRedraw(); } void appl::widget::VideoDisplay::setZeusMedia(ememory::SharedPtr _property, uint32_t _mediaId) { // Stop playing in all case... stop(); if (m_decoder != null) { m_decoder->uninit(); } // Clear the old interface m_decoder.reset(); // Create a new interface if (m_decoder == null) { m_decoder = ememory::makeShared(); } if (m_decoder == null) { APPL_ERROR("Can not create sharedPtr on decoder ..."); return; } m_haveDuration = false; m_decoder->init(_property, _mediaId); markToRedraw(); } bool appl::widget::VideoDisplay::isPlaying() { return m_isPalying; } void appl::widget::VideoDisplay::play() { if (m_decoder == null) { APPL_WARNING("Request play with no associated decoder"); return; } m_isPalying = true; if (m_decoder->getState() != gale::Thread::state::stop) { // The thread is already active ==> then it is just in pause ... APPL_DEBUG("Already started"); return; } // Start decoder, this is maybe not the good point, but if we configure a decoder, it is to use it ... m_decoder->start(); //TODO: Set an option to river to auto-generate dot: m_audioManager->generateDotAll("out/local_player_flow.dot"); } void appl::widget::VideoDisplay::changeVolume(const float& _value) { if (m_audioManager != null) { m_audioManager->setVolume("MASTER", _value); } } void appl::widget::VideoDisplay::changeLight(const float& _value) { m_light = _value; m_color.setR(m_light); m_color.setG(m_light); m_color.setB(m_light); m_color.setA(1.0); } void appl::widget::VideoDisplay::pause() { m_isPalying = false; } void appl::widget::VideoDisplay::stop() { m_isPalying = false; if ( m_decoder != null && m_decoder->getState() != gale::Thread::state::stop) { APPL_ERROR("Stop Decoder"); m_decoder->stop(); m_currentTime = 0; } if (m_audioInterface != null) { APPL_ERROR("Stop audio interface"); // Stop audio interface m_audioInterface->stop(); // wait a little to be sure it is done correctly: // TODO : Set this in an asynchronous loop ... m_audioInterface.reset(); } } void appl::widget::VideoDisplay::onDraw() { if (m_VBO->bufferSize(m_vboIdCoord) <= 0) { APPL_WARNING("Nothink to draw..."); return; } /* if ( m_resource[iii] == null || m_resource2 == null) { // this is a normale case ... the user can choice to have no image ... return; } */ if (m_GLprogram == null) { APPL_ERROR("No shader ..."); return; } gale::openGL::disable(gale::openGL::flag_depthTest); mat4 tmpMatrix = gale::openGL::getMatrix()*m_matrixApply; m_GLprogram->use(); m_GLprogram->uniformMatrix(m_GLMatrix, tmpMatrix); // TextureID if (m_resource[m_useElement] != null) { m_GLprogram->setTexture0(m_GLtexID, m_resource[m_useElement]->getRendererId()); } // position: m_GLprogram->sendAttributePointer(m_GLPosition, m_VBO, m_vboIdCoord); // Texture: m_GLprogram->sendAttributePointer(m_GLtexture, m_VBO, m_vboIdCoordTex); // color: m_GLprogram->sendAttributePointer(m_GLColor, m_VBO, m_vboIdColor); // Request the draw of the elements: gale::openGL::drawArrays(gale::openGL::renderMode::triangle, 0, m_VBO->bufferSize(m_vboIdCoord)); m_GLprogram->unUse(); } void appl::widget::VideoDisplay::printPart(const vec2& _displayStart, const vec2& _size, const vec2& _sourcePosStart, const vec2& _sourcePosStop) { //EWOL_ERROR("Debug image " << m_filename << " ==> " << m_position << " " << _size << " " << _sourcePosStart << " " << _sourcePosStop); vec3 point(_displayStart.x(),_displayStart.y(), 0.0f); vec2 tex(_sourcePosStart.x(),_sourcePosStop.y()); m_VBO->pushOnBuffer(m_vboIdCoord, point); m_VBO->pushOnBuffer(m_vboIdCoordTex, tex); m_VBO->pushOnBuffer(m_vboIdColor, m_color); tex.setValue(_sourcePosStop.x(),_sourcePosStop.y()); point.setX(_displayStart.x() + _size.x()); point.setY(_displayStart.y()); m_VBO->pushOnBuffer(m_vboIdCoord, point); m_VBO->pushOnBuffer(m_vboIdCoordTex, tex); m_VBO->pushOnBuffer(m_vboIdColor, m_color); tex.setValue(_sourcePosStop.x(),_sourcePosStart.y()); point.setX(_displayStart.x() + _size.x()); point.setY(_displayStart.y() + _size.y()); m_VBO->pushOnBuffer(m_vboIdCoord, point); m_VBO->pushOnBuffer(m_vboIdCoordTex, tex); m_VBO->pushOnBuffer(m_vboIdColor, m_color); m_VBO->pushOnBuffer(m_vboIdCoord, point); m_VBO->pushOnBuffer(m_vboIdCoordTex, tex); m_VBO->pushOnBuffer(m_vboIdColor, m_color); tex.setValue(_sourcePosStart.x(),_sourcePosStart.y()); point.setX(_displayStart.x()); point.setY(_displayStart.y() + _size.y()); m_VBO->pushOnBuffer(m_vboIdCoord, point); m_VBO->pushOnBuffer(m_vboIdCoordTex, tex); m_VBO->pushOnBuffer(m_vboIdColor, m_color); tex.setValue(_sourcePosStart.x(),_sourcePosStop.y()); point.setX(_displayStart.x()); point.setY(_displayStart.y()); m_VBO->pushOnBuffer(m_vboIdCoord, point); m_VBO->pushOnBuffer(m_vboIdCoordTex, tex); m_VBO->pushOnBuffer(m_vboIdColor, m_color); m_VBO->flush(); } void appl::widget::VideoDisplay::onRegenerateDisplay() { //!< Check if we really need to redraw the display, if not needed, we redraw the previous data ... if (needRedraw() == false) { return; } // remove previous data m_VBO->clear(); // set the somposition properties : m_position = vec3(0,0,0); if (m_decoder != null) { ivec2 tmp = m_decoder->getSize(); vec2 realSize(tmp.x(), tmp.y()); vec2 displaySize = m_size; float aspectRatioReal = realSize.x() / realSize.y(); float displayRatioReal = displaySize.x() / displaySize.y(); vec2 startPos(0,0); if (aspectRatioReal == displayRatioReal) { // nothing to do ... } else if (aspectRatioReal < displayRatioReal) { // the display is more width than the video displaySize.setX(displaySize.y()*realSize.x()/realSize.y()); startPos.setX((m_size.x()-displaySize.x())*0.5f); } else { // The display is more height than the video displaySize.setY(displaySize.x()*realSize.y()/realSize.x()); startPos.setY((m_size.y()-displaySize.y())*0.5f); } printPart(startPos, displaySize, vec2(0,0), vec2(float(m_videoSize.x())/float(m_imageSize.x()), float(m_videoSize.y())/float(m_imageSize.y()))); } else { // nothing to display ... } markToRedraw(); } void appl::widget::VideoDisplay::periodicEvent(const ewol::event::Time& _event) { if (m_isPalying == true) { APPL_VERBOSE("tick: " << _event); m_currentTime += _event.getDeltaCallDuration(); } if (m_decoder == null) { return; } if (m_decoder->m_seekApply > echrono::Duration(-1)) { m_currentTime = m_decoder->m_seekApply; APPL_ERROR("=========================================================="); APPL_ERROR("Apply seek position : " << m_currentTime); APPL_ERROR("=========================================================="); m_decoder->m_seekApply = echrono::Duration(-1); if (m_audioInterface != null) { m_audioInterface->clearInternalBuffer(); } } // SET AUDIO: bool getSomething = false; bool cleaning = true; while (cleaning == true) { int32_t idSlot = m_decoder->audioGetOlderSlot(); if ( idSlot != -1 && m_currentTime > m_decoder->m_audioPool[idSlot].m_time) { APPL_VERBOSE("Get Slot AUDIO " << m_currentTime << " > " << m_decoder->m_audioPool[idSlot].m_time); if (m_audioInterface == null) { // start audio interface the first time we need it APPL_ERROR("=========================================================="); APPL_ERROR("== Presence of Audio: " << m_decoder->haveAudio() << " =="); APPL_ERROR("=========================================================="); if (m_decoder->haveAudio() == true) { m_audioInterface = m_audioManager->createOutput(m_decoder->audioGetSampleRate(), m_decoder->audioGetChannelMap(), m_decoder->audioGetFormat(), "speaker"); if(m_audioInterface == null) { APPL_ERROR("Can not create Audio interface"); } m_audioInterface->setReadwrite(); m_audioInterface->start(); } } if (m_audioInterface != null) { int32_t nbSample = m_decoder->m_audioPool[idSlot].m_buffer.size() / audio::getFormatBytes(m_decoder->m_audioPool[idSlot].m_format) / m_decoder->m_audioPool[idSlot].m_map.size(); m_audioInterface->write(&m_decoder->m_audioPool[idSlot].m_buffer[0], nbSample); } m_decoder->m_audioPool[idSlot].m_isUsed = false; getSomething = true; } else { cleaning = false; } } // SET VIDEO: cleaning = true; int32_t nbDumpFrame = 0; while (cleaning == true) { int32_t idSlot = m_decoder->videoGetOlderSlot(); // check the slot is valid and check display time of the element: if ( idSlot != -1 && m_currentTime > m_decoder->m_videoPool[idSlot].m_time) { APPL_WARNING("Get Slot VIDEO " << m_currentTime << " > " << m_decoder->m_audioPool[idSlot].m_time); m_resource[m_useElement]->get().swap(m_decoder->m_videoPool[idSlot].m_image); m_resource[m_useElement]->flush(); m_useElement++; if (m_useElement == ZEUS_VIDEO_PLAYER_MULTIPLE_BUFFER) { m_useElement = 0; } m_imageSize = m_resource[m_useElement]->get().getGPUSize(); ivec2 tmpSize = m_decoder->m_videoPool[idSlot].m_imageRealSize; m_decoder->m_videoPool[idSlot].m_imageRealSize = m_videoSize; m_videoSize = tmpSize; m_decoder->m_videoPool[idSlot].m_isUsed = false; nbDumpFrame++; getSomething = true; } else { cleaning = false; } } if (nbDumpFrame != 0) { m_nbFramePushed++; } // Display FPS ... m_LastResetCounter += _event.getDeltaCallDuration(); if (m_LastResetCounter > echrono::seconds(1)) { m_LastResetCounter.reset(); signalFps.emit(m_nbFramePushed); signalOther.emit(etk::toString(m_nbFramePushed) + " " + etk::toString(m_resource[m_useElement]->get().getSize()) + " " + etk::toString(m_resource[m_useElement]->get().getGPUSize()) ); m_nbFramePushed = 0; } if ( getSomething == false && m_isPalying == true) { //m_currentTime -= _event.getDeltaCallDuration(); } else { signalPosition.emit(m_currentTime); if (m_haveDuration == false) { signalDuration.emit(m_decoder->getDuration()); m_haveDuration = true; } } // TODO : Chek if this is needed, the display configuration not change too much ... markToRedraw(); // TODO: understand why this take 4 seconds to detect end ... maybe check end with the end read of the file ... if ( m_haveDuration == true && m_decoder->getDuration() > echrono::milliseconds(10)) { if (m_currentTime >= m_decoder->getDuration() /*- echrono::milliseconds(1000)*/) { APPL_WARNING("Finish playing"); signalFinish.emit(); stop(); } } } void appl::widget::VideoDisplay::seek(const echrono::Duration& _time) { APPL_PRINT("seek request = " << _time); if (m_decoder == null) { return; } m_decoder->seek(_time); }