Status MovieExporter::convertToGif( QString ffmpeg, QString strIn, QString strOut ) { // http://superuser.com/questions/556029/ // generate a palette QString strGifPalette = mTempWorkDir + "/palette.png"; QString strCmd1 = QString( "\"%1\"" ).arg( ffmpeg ); strCmd1 += " -y"; strCmd1 += QString( " -i \"%1\"" ).arg( strIn ); strCmd1 += " -vf scale=320:-1:flags=lanczos,palettegen"; strCmd1 += QString( " \"%1\"" ).arg( strGifPalette ); STATUS_CHECK( executeFFMpegCommand( strCmd1 ) ); // Output the GIF using the palette: QString strCmd2 = QString( "\"%1\"" ).arg( ffmpeg ); strCmd2 += " -y"; strCmd2 += QString( " -i \"%1\"" ).arg( strIn ); strCmd2 += QString( " -i \"%1\"" ).arg( strGifPalette ); strCmd2 += " -filter_complex \"scale=-1:-1:flags=lanczos[x];[x][1:v]paletteuse\""; strCmd2 += QString( " \"%1\"" ).arg( strOut ); STATUS_CHECK( executeFFMpegCommand( strCmd2 ) ); return Status::OK; }
Status MovieExporter::combineVideoAndAudio( QString ffmpegPath, QString strOutputFile ) { if ( mCanceled ) { return Status::CANCELED; } //int exportFps = mDesc.videoFps; const QString imgPath = mTempWorkDir + IMAGE_FILENAME; const QString tempAudioPath = mTempWorkDir + "/tmpaudio.wav"; const QSize exportSize = mDesc.exportSize; QString strCmd = QString("\"%1\"").arg( ffmpegPath ); strCmd += QString( " -f image2"); strCmd += QString( " -framerate %1" ).arg( mDesc.fps ); strCmd += QString( " -pix_fmt yuv420p" ); strCmd += QString( " -start_number %1" ).arg( mDesc.startFrame ); //strCmd += QString( " -r %1" ).arg( exportFps ); strCmd += QString( " -i \"%1\" " ).arg( imgPath ); if ( QFile::exists( tempAudioPath ) ) { strCmd += QString( " -i \"%1\" " ).arg( tempAudioPath ); } strCmd += QString( " -s %1x%2" ).arg( exportSize.width() ).arg( exportSize.height() ); strCmd += " -y"; strCmd += QString(" \"%1\"" ).arg( strOutputFile ); STATUS_CHECK( executeFFMpegCommand( strCmd ) ); return Status::OK; }
/** * OpenGL display function. * * This function is called every 25 milliseconds by the update * function. */ void glutDisplay (void) { /** * Update every node of OpenNI. */ g_Context.WaitOneUpdateAll(g_DepthGenerator); glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(40.0, 1.05, 1.0, 10000.0); gluLookAt(320.0, -300.0, 4200.0, 320.0, 240.0, 1500.0, 0.0,-1.0, 0.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glPushMatrix(); // Checking fot game starting and finishing g_SFBgame.checkUsers(); if (g_SFBgame.isGameOn()) { g_UserDetector.changeStopDetection(true); g_SFBgame.checkGameOver(); if (!g_SFBgame.isGameOver()) { g_BusterDetector -> detectPose(); g_IceRodDetector -> detectPose(); } else { STATUS_CHECK(g_Context.StopGeneratingAll(), "Context generation shutdown"); } } else { // Detects poses g_ZamusDetector -> detectPose(); g_LinqDetector -> detectPose(); } /** * Use the draw functions of every class to display the game with * OpenGL. */ g_SceneRenderer.drawScene(); g_SFBgame.drawFireBalls(); g_SFBgame.drawGameInfo(); g_SFBgame.nextFrame(); glPopMatrix(); glutSwapBuffers(); }
Status MovieExporter::twoPassEncoding( QString ffmpeg, QString strOutputFile ) { QString strTempVideo = mTempWorkDir + "/Temp1.mp4"; qDebug() << "TempVideo=" << strTempVideo; combineVideoAndAudio( ffmpeg, strTempVideo ); if ( strOutputFile.endsWith( "gif" ) ) { STATUS_CHECK( convertToGif( ffmpeg, strTempVideo, strOutputFile ) ); } else { STATUS_CHECK( convertVideoAgain( ffmpeg, strTempVideo, strOutputFile ) ); } return Status::OK; }
Status MovieExporter::run(const Object* obj, const ExportMovieDesc& desc, std::function<void( float )> progress ) { progress( 0.f ); QString ffmpegPath = ffmpegLocation(); qDebug() << ffmpegPath; if ( !QFile::exists( ffmpegPath ) ) { qDebug() << "Please place ffmpeg.exe in " << ffmpegPath << " directory"; return Status::ERROR_FFMPEG_NOT_FOUND; } STATUS_CHECK( checkInputParameters( desc ) ); mDesc = desc; qDebug() << "OutFile: " << mDesc.strFileName; // Setup temporary folder if ( !mTempDir.isValid() ) { Q_ASSERT( false && "Cannot create temp folder." ); return Status::FAIL; } mTempWorkDir = mTempDir.path(); progress( 0.03f ); if ( !desc.strFileName.endsWith( "gif" ) ) { STATUS_CHECK( assembleAudio( obj, ffmpegPath, progress ) ); } progress( 0.10f ); STATUS_CHECK( generateImageSequence( obj, progress ) ); progress( 0.99f ); twoPassEncoding( ffmpegPath, desc.strFileName ); progress( 1.0f ); return Status::OK; }
Status MovieExporter::convertVideoAgain( QString ffmpegPath, QString strIn, QString strOut ) { QString strCmd = QString("\"%1\"").arg( ffmpegPath ); strCmd += QString( " -i \"%1\" " ).arg( strIn ); strCmd += QString( " -pix_fmt yuv420p" ); strCmd += " -y"; strCmd += QString(" \"%1\"" ).arg( strOut ); STATUS_CHECK( executeFFMpegCommand( strCmd ) ); return Status::OK; }
int http_status (int fd, int no) { http_code *c; char buf[128]; int rc; STATUS_CHECK(no); STATUS_ASSIGN(no,c); sprintf (buf, HTTP_PROTOCOL" %d %s\r\n", c->num, c->str); rc = write (fd, buf, strlen(buf)); return (rc); }
/** * Initialize XN functions */ void initialize() { ImageMetaData imageMD; XnStatus status; int dummy; srand ( time(NULL) ); // Initializing context and checking for enumeration errors status = g_Context.InitFromXmlFile(XML_CONFIG_FILE, &g_Error); checkEnumError(status, g_Error); // Finding nodes and checking for errors STATUS_CHECK(g_Context.FindExistingNode(XN_NODE_TYPE_DEPTH, g_DepthGenerator), "Finding depth node"); STATUS_CHECK(g_Context.FindExistingNode(XN_NODE_TYPE_SCENE, g_SceneAnalyzer), "Finding scene analizer"); // Note: when the image generation node is handled the program gets // too slow. // STATUS_CHECK(g_Context.FindExistingNode(XN_NODE_TYPE_IMAGE, g_ImageGenerator), "Finding image node"); // Set view point of Depth generator to the image generator point of // view. // STATUS_CHECK(g_DepthGenerator.GetAlternativeViewPointCap().SetViewPoint(g_ImageGenerator), "Set View Point"); STATUS_CHECK(g_Context.FindExistingNode(XN_NODE_TYPE_USER, g_UserGenerator), "Finding user node"); //g_ImageGenerator.GetMetaData(imageMD); // Checking camera pixel format //if (imageMD.PixelFormat() != XN_PIXEL_FORMAT_RGB24) { // reportError("Camera format not supported...!\n"); //} // Checking user generator capabilities if(!g_UserGenerator.IsCapabilitySupported(XN_CAPABILITY_SKELETON)) { reportError("Skeleton capability not supported\n"); } if(!g_UserGenerator.IsCapabilitySupported(XN_CAPABILITY_POSE_DETECTION)) { reportError("Pose detection capability not supported\n"); } printf("Number of players: "); dummy = scanf("%d", &g_MaxPlayers); printf("\n"); //Initialize user detector object g_UserDetector = UserDetector(g_UserGenerator, g_DepthGenerator); g_UserDetector.registerCallbacks(); g_ZamusDetector = new Zamus(&g_UserDetector); g_LinqDetector = new Linq(&g_UserDetector); g_BusterDetector = new BusterDetector(g_ZamusDetector, &g_UserDetector); g_IceRodDetector = new IceRodDetector(g_LinqDetector, &g_UserDetector); // Initialize image render object g_SceneRenderer = SceneRenderer(&g_ImageGenerator, &g_DepthGenerator, &g_SceneAnalyzer, &g_UserDetector, g_ZamusDetector, g_LinqDetector); STATUS_CHECK(g_Context.StartGeneratingAll(), "Context generation"); g_SFBgame = SuperFiremanBrothers(&g_UserDetector, &g_SceneAnalyzer, g_ZamusDetector, g_LinqDetector, g_MaxPlayers ); }
void TemplateDispatch<T>::dispatchKernel(T run_args, hsa_signal_t& signal, const Launch_params_t lparm) { hsa_dispatch_packet_t run_Aql; HSAContextKaveriImpl::KernelImpl *p_impl = (HSAContextKaveriImpl::KernelImpl *) m_p_kernel; hsa_status_t err; status_t status = STATUS_SUCCESS; /* Create a signal to wait for the dispatch to finish. */ err = hsa_signal_create(1, 0, NULL, &signal); STATUS_CHECK(err, __LINE__); /* Setup this call to this kernel dispatch packet from scratch. */ memset(&run_Aql, 0, sizeof(run_Aql)); run_Aql.completion_signal = signal; /* Set the dimensions passed from the application */ run_Aql.dimensions = (uint16_t) lparm.ndim; run_Aql.grid_size_x = lparm.gdims[0]; run_Aql.workgroup_size_x = lparm.ldims[0]; if (lparm.ndim > 1) { run_Aql.grid_size_y = lparm.gdims[1]; run_Aql.workgroup_size_y = lparm.ldims[1]; } else { run_Aql.grid_size_y = 1; run_Aql.workgroup_size_y = 1; } if (lparm.ndim > 2) { run_Aql.grid_size_z = lparm.gdims[2]; run_Aql.workgroup_size_z = lparm.ldims[2]; } else { run_Aql.grid_size_z = 1; run_Aql.workgroup_size_z = 1; } /* In the future, we may use environment variables for some of these */ run_Aql.header.type = HSA_PACKET_TYPE_DISPATCH; run_Aql.header.acquire_fence_scope = 2; run_Aql.header.release_fence_scope = 2; run_Aql.header.barrier = 1; run_Aql.group_segment_size = p_impl->hsaCodeDescriptor->workgroup_group_segment_byte_size; run_Aql.private_segment_size = p_impl->hsaCodeDescriptor->workitem_private_segment_byte_size; /* copy args from the custom run_args structure */ /* FIXME We should align kernel_arg_buffer because run_args is aligned */ memcpy(run_kernel_arg_buffer, &run_args, sizeof(run_args)); /* Bind kernelcode to the packet. */ run_Aql.kernel_object_address = p_impl->hsaCodeDescriptor->code.handle; /* Bind kernel argument buffer to the aql packet. */ run_Aql.kernarg_address = (uint64_t) run_kernel_arg_buffer; /* Obtain the current queue write index. increases with each call to kernel */ uint64_t index = hsa_queue_load_write_index_relaxed( p_impl->context->commandQueue); /* printf("DEBUG:Call #%d to kernel \"%s\" \n",(int) index+1,"run"); */ /* Write the run_Aql packet at the calculated queue index address. */ const uint32_t queueMask = p_impl->context->commandQueue->size - 1; const uint32_t pos = index & queueMask; ((hsa_dispatch_packet_t*) (p_impl->context->commandQueue->base_address))[pos] = run_Aql; /* Increment the write index and ring the doorbell to dispatch the kernel. */ hsa_queue_store_write_index_relaxed(p_impl->context->commandQueue, index + 1); hsa_signal_store_relaxed(p_impl->context->commandQueue->doorbell_signal, index); return; }
/** Begin exporting the movie described by exportDesc. * * @param[in] obj An Object containing the animation to export. * @param[in] desc A structure containing all the export parameters. * See ExportMovieDesc. * @param[out] majorProgress A function to update the major progress bar. * The major progress bar goes from 0-100% only * one time, representing the overall progress of * the export. The first float parameter is * the current progress %, and the second is * the desired progress when the next sub-task * completes. This should only be called at the * beginning of a subtask. * @param[out] minorProgress A function to update the minor progress bar. * The minor progress bar goes from 0-100% for * each sub-task of the exporting process. * It is up to minor progress to update the * major progress bar to reflect the sub-task * progress. * @param[out] progressMessage A function ot update the progres bar * message. The messages will describe * the current sub-task of the exporting * process. * * @return Returns Status:OK on success, or Status::FAIL on error. */ Status MovieExporter::run(const Object* obj, const ExportMovieDesc& desc, std::function<void(float, float)> majorProgress, std::function<void(float)> minorProgress, std::function<void(QString)> progressMessage) { majorProgress(0.f, 0.03f); minorProgress(0.f); progressMessage(QObject::tr("Checking environment...")); clock_t t1 = clock(); QString ffmpegPath = ffmpegLocation(); qDebug() << ffmpegPath; if (!QFile::exists(ffmpegPath)) { #ifdef _WIN32 qCritical() << "Please place ffmpeg.exe in " << ffmpegPath << " directory"; #else qCritical() << "Please place ffmpeg in " << ffmpegPath << " directory"; #endif return Status::ERROR_FFMPEG_NOT_FOUND; } STATUS_CHECK(checkInputParameters(desc)); mDesc = desc; qDebug() << "OutFile: " << mDesc.strFileName; // Setup temporary folder if (!mTempDir.isValid()) { Q_ASSERT(false && "Cannot create temp folder."); return Status::FAIL; } mTempWorkDir = mTempDir.path(); minorProgress(0.f); if (desc.strFileName.endsWith("gif", Qt::CaseInsensitive)) { majorProgress(0.03f, 1.f); progressMessage("Generating gif..."); minorProgress(0.f); STATUS_CHECK(generateGif(obj, ffmpegPath, desc.strFileName, minorProgress)); } else { majorProgress(0.03f, 0.25f); progressMessage("Assembling audio..."); minorProgress(0.f); STATUS_CHECK(assembleAudio(obj, ffmpegPath, minorProgress)); minorProgress(1.f); majorProgress(0.25f, 1.f); progressMessage("Generating movie..."); STATUS_CHECK(generateMovie(obj, ffmpegPath, desc.strFileName, minorProgress)); } minorProgress(1.f); majorProgress(1.f, 1.f); progressMessage(QObject::tr("Done")); clock_t t2 = clock() - t1; qDebug("MOVIE = %.1f sec", static_cast<double>(t2 / CLOCKS_PER_SEC)); return Status::OK; }
/** Exports obj to a gif image at strOut using FFmpeg. * * @param[in] obj An Object containing the animation to export. * @param[in] ffmpegPath The path to the FFmpeg binary. * @param[in] strOut The output path. Should end with .gif. * @param[out] progress A function that takes one float argument * (the percentage of the gif generation complete) and * may display the output to the user in any way it * sees fit. * * @return Returns the final status of the operation (ok or canceled) */ Status MovieExporter::generateGif( const Object* obj, QString ffmpegPath, QString strOut, std::function<void(float)> progress) { if (mCanceled) { return Status::CANCELED; } // Frame generation setup int frameStart = mDesc.startFrame; int frameEnd = mDesc.endFrame; const QSize exportSize = mDesc.exportSize; bool transparency = false; QString strCameraName = mDesc.strCameraName; bool loop = mDesc.loop; int bytesWritten; auto cameraLayer = static_cast<LayerCamera*>(obj->findLayerByName(strCameraName, Layer::CAMERA)); if (cameraLayer == nullptr) { cameraLayer = obj->getLayersByType< LayerCamera >().front(); } int currentFrame = frameStart; /* We create an image with the correct dimensions and background * color here and then copy this and draw over top of it to * generate each frame. This is faster than having to generate * a new background image for each frame. */ QImage imageToExportBase(exportSize, QImage::Format_ARGB32_Premultiplied); QColor bgColor = Qt::white; if (transparency) { bgColor.setAlpha(0); } imageToExportBase.fill(bgColor); QSize camSize = cameraLayer->getViewSize(); QTransform centralizeCamera; centralizeCamera.translate(camSize.width() / 2, camSize.height() / 2); // Build FFmpeg command QString strCmd = QString("\"%1\"").arg(ffmpegPath); strCmd += QString(" -f rawvideo -pixel_format bgra"); strCmd += QString(" -video_size %1x%2").arg(exportSize.width()).arg(exportSize.height()); strCmd += QString(" -framerate %1").arg(mDesc.fps); strCmd += " -i -"; strCmd += " -y"; strCmd += " -filter_complex \"[0:v]palettegen [p]; [0:v][p] paletteuse\""; strCmd += QString(" -loop %1").arg(loop ? "0" : "-1"); strCmd += QString(" \"%1\"").arg(strOut); // Run FFmpeg command STATUS_CHECK(executeFFMpegPipe(strCmd, progress, [&](QProcess& ffmpeg, int framesProcessed) { /* The GIF FFmpeg command requires the entires stream to be * written before FFmpeg can encode the GIF. This is because * the generated pallete is based off of the colors in all * frames. The only way to avoid this would be to generate * all the frames twice and run two separate commands, which * would likely have unacceptable speed costs. */ Q_UNUSED(framesProcessed); if(currentFrame > frameEnd) { ffmpeg.closeWriteChannel(); return false; } QImage imageToExport = imageToExportBase.copy(); QPainter painter(&imageToExport); QTransform view = cameraLayer->getViewAtFrame(currentFrame); painter.setWorldTransform(view * centralizeCamera); painter.setWindow(QRect(0, 0, camSize.width(), camSize.height())); obj->paintImage(painter, currentFrame, false, true); bytesWritten = ffmpeg.write(reinterpret_cast<const char*>(imageToExport.constBits()), imageToExport.byteCount()); Q_ASSERT(bytesWritten == imageToExport.byteCount()); currentFrame++; return true; })); return Status::OK; }
/** Exports obj to a movie image at strOut using FFmpeg. * * @param[in] obj An Object containing the animation to export. * @param[in] ffmpegPath The path to the FFmpeg binary. * @param[in] strOutputFile The output path. Should end with .gif. * @param[out] progress A function that takes one float argument * (the percentage of the gif generation complete) and * may display the output to the user in any way it * sees fit. * * The movie formats supported by this operation are any file * formats that the referenced FFmpeg binary supports and that have * the required features (ex. video and audio support) * * @return Returns the final status of the operation (ok or canceled) */ Status MovieExporter::generateMovie( const Object* obj, QString ffmpegPath, QString strOutputFile, std::function<void(float)> progress) { if (mCanceled) { return Status::CANCELED; } // Frame generation setup int frameStart = mDesc.startFrame; int frameEnd = mDesc.endFrame; const QSize exportSize = mDesc.exportSize; bool transparency = mDesc.alpha; QString strCameraName = mDesc.strCameraName; bool loop = mDesc.loop; auto cameraLayer = static_cast<LayerCamera*>(obj->findLayerByName(strCameraName, Layer::CAMERA)); if (cameraLayer == nullptr) { cameraLayer = obj->getLayersByType< LayerCamera >().front(); } int currentFrame = frameStart; /* We create an image with the correct dimensions and background * color here and then copy this and draw over top of it to * generate each frame. This is faster than having to generate * a new background image for each frame. */ QImage imageToExportBase(exportSize, QImage::Format_ARGB32_Premultiplied); QColor bgColor = Qt::white; if (transparency) { bgColor.setAlpha(0); } imageToExportBase.fill(bgColor); QSize camSize = cameraLayer->getViewSize(); QTransform centralizeCamera; centralizeCamera.translate(camSize.width() / 2, camSize.height() / 2); int failCounter = 0; /* Movie export uses a "sliding window" to reduce memory usage * while having a relatively small impact on speed. This basically * means that there is a maximum number of frames that can be waiting * to be encoded by ffmpeg at any one time. The limit is set by the * frameWindow variable which is designed to take up a maximum of * about 1GB of memory */ int frameWindow = static_cast<int>(1e9 / (camSize.width() * camSize.height() * 4.0)); // Build FFmpeg command //int exportFps = mDesc.videoFps; const QString tempAudioPath = mTempWorkDir + "/tmpaudio.wav"; QString strCmd = QString("\"%1\"").arg(ffmpegPath); strCmd += QString(" -f rawvideo -pixel_format bgra"); strCmd += QString(" -video_size %1x%2").arg(exportSize.width()).arg(exportSize.height()); strCmd += QString(" -framerate %1").arg(mDesc.fps); //strCmd += QString( " -r %1").arg( exportFps ); strCmd += QString(" -i -"); strCmd += QString(" -threads %1").arg(QThread::idealThreadCount() == 1 ? 0 : QThread::idealThreadCount()); if (QFile::exists(tempAudioPath)) { strCmd += QString(" -i \"%1\" ").arg(tempAudioPath); } if (strOutputFile.endsWith(".apng", Qt::CaseInsensitive)) { strCmd += QString(" -plays %1").arg(loop ? "0" : "1"); } if (strOutputFile.endsWith("mp4", Qt::CaseInsensitive)) { strCmd += QString(" -pix_fmt yuv420p"); } if (strOutputFile.endsWith(".avi", Qt::CaseInsensitive)) { strCmd += " -q:v 5"; } strCmd += " -y"; strCmd += QString(" \"%1\"").arg(strOutputFile); // Run FFmpeg command STATUS_CHECK(executeFFMpegPipe(strCmd, progress, [&](QProcess& ffmpeg, int framesProcessed) { if(framesProcessed < 0) { failCounter++; } if(currentFrame > frameEnd) { ffmpeg.closeWriteChannel(); return false; } if((currentFrame - frameStart <= framesProcessed + frameWindow || failCounter > 10) && currentFrame <= frameEnd) { QImage imageToExport = imageToExportBase.copy(); QPainter painter(&imageToExport); QTransform view = cameraLayer->getViewAtFrame(currentFrame); painter.setWorldTransform(view * centralizeCamera); painter.setWindow(QRect(0, 0, camSize.width(), camSize.height())); obj->paintImage(painter, currentFrame, false, true); painter.end(); // Should use sizeInBytes instead of byteCount to support large images, // but this is only supported in QT 5.10+ int bytesWritten = ffmpeg.write(reinterpret_cast<const char*>(imageToExport.constBits()), imageToExport.byteCount()); Q_ASSERT(bytesWritten == imageToExport.byteCount()); currentFrame++; failCounter = 0; return true; } return false; })); return Status::OK; }
/** Combines all audio tracks in obj into a single file. * * @param[in] obj * @param[in] ffmpegPath * @param[out] progress A function that takes one float argument * (the percentage of the audio assembly complete) and * may display the output to the user in any way it * sees fit. * * @return Returns the final status of the operation. Ok if successful, * or safe if there was intentionally no output. */ Status MovieExporter::assembleAudio(const Object* obj, QString ffmpegPath, std::function<void(float)> progress) { // Quicktime assemble call const int startFrame = mDesc.startFrame; const int endFrame = mDesc.endFrame; const int fps = mDesc.fps; Q_ASSERT(startFrame >= 0); Q_ASSERT(endFrame >= startFrame); QDir dir(mTempWorkDir); Q_ASSERT(dir.exists()); QString tempAudioPath = mTempWorkDir + "/tmpaudio0.wav"; qDebug() << "TempAudio=" << tempAudioPath; std::vector< SoundClip* > allSoundClips; std::vector< LayerSound* > allSoundLayers = obj->getLayersByType<LayerSound>(); for (LayerSound* layer : allSoundLayers) { layer->foreachKeyFrame([&allSoundClips](KeyFrame* key) { allSoundClips.push_back(static_cast<SoundClip*>(key)); }); } int clipCount = 0; QString strCmd, filterComplex, amergeInput, panChannelLayout; strCmd += QString("\"%1\"").arg(ffmpegPath); int wholeLen = qCeil((endFrame - startFrame) * 44100.0 / fps); for (auto clip : allSoundClips) { if (mCanceled) { return Status::CANCELED; } // Add sound file as input strCmd += QString(" -i \"%1\"").arg(clip->fileName()); // Offset the sound to its correct position // See https://superuser.com/questions/716320/ffmpeg-placing-audio-at-specific-location filterComplex += QString("[%1:a:0] aformat=sample_fmts=fltp:sample_rates=44100:channel_layouts=mono,volume=1,adelay=%2S|%2S,apad=whole_len=%3[ad%1];") .arg(clipCount).arg(qRound(44100.0 * (clip->pos() - 1) / fps)).arg(wholeLen); amergeInput += QString("[ad%1]").arg(clipCount); panChannelLayout += QString("c%1+").arg(clipCount); clipCount++; } // Remove final '+' panChannelLayout.chop(1); // Output arguments // Mix audio strCmd += QString(" -filter_complex \"%1%2 amerge=inputs=%3, pan=mono|c0=%4 [out]\"") .arg(filterComplex).arg(amergeInput).arg(clipCount).arg(panChannelLayout); // Convert audio file: 44100Hz sampling rate, stereo, signed 16 bit little endian // Supported audio file types: wav, mp3, ogg... ( all file types supported by ffmpeg ) strCmd += " -ar 44100 -acodec pcm_s16le -ac 2 -map \"[out]\" -y"; // Trim audio strCmd += QString(" -ss %1").arg((startFrame - 1) / static_cast<double>(fps)); strCmd += QString(" -to %1").arg(endFrame / static_cast<double>(fps)); // Output path strCmd += " " + mTempWorkDir + "/tmpaudio.wav"; STATUS_CHECK(executeFFMpeg(strCmd, progress)); qDebug() << "audio file: " + tempAudioPath; return Status::OK; }