用管道从CMD窗口读FFMPEG执行信息的方法

From Ffmpeg工程组

Jump to: navigation, search

用管道从CMD窗口读FFMPEG执行信息的方法

从www.vckbase.com上面看到管理与CMD通信的方法,正好适用于FFMPEG,当然也适用于MENCODER等所有CMD窗口程序。
void function()
{
   HANDLE   hSTDINWrite, hSTDINRead;       // 用于重定向子进程输入的句柄   
   HANDLE   hSTDOUTWrite, hSTDOUTRead;     // 用于重定向子进程输出的句柄   
   HANDLE   hSTDERRWrite, hSTDERRRead;     // 用于重定向子进程输出的句柄       
   SECURITY_ATTRIBUTES   sa;      
   sa.bInheritHandle = TRUE;   
   sa.lpSecurityDescriptor = NULL;   
   sa.nLength = sizeof(sa);      
   // 创建子进程输出匿名管道   
   if( !CreatePipe(&hSTDOUTRead, &hSTDOUTWrite, &sa, 0) )   
   {   
       //AfxMessageBox("Create   STDOUT   pipe   failed");   
       return;   
   }     
   // 创建子进程输入匿名管道   
   if( !CreatePipe(&hSTDINRead, &hSTDINWrite, &sa, 0) )   
   {   
//        AfxMessageBox("Create   STDIN   pipe   failed");   
       return;   
   } 
   // 创建子进程出错匿名管道   
/*    if( !CreatePipe(&hSTDERRRead, &hSTDERRWrite, &sa, 0) )   
   {   
       AfxMessageBox("Create   STDIN   pipe   failed");   
       return;   
   } */
   PROCESS_INFORMATION  pi; 
   ZeroMemory(&pi, sizeof(pi));   
   STARTUPINFO  si; 
   GetStartupInfo(&si);  
   si.cb = sizeof(STARTUPINFO);
   si.dwFlags = STARTF_USESTDHANDLES   |   STARTF_USESHOWWINDOW;
   si.wShowWindow = SW_SHOW;//SW_HIDE;
   si.hStdInput   =   hSTDINRead;      //重定向子进程输入   
   si.hStdOutput   =   hSTDOUTWrite;   //重定向子进程输入    
   si.hStdError = hSTDOUTWrite; //GetStdHandle( STD_ERROR_HANDLE );    
   char cmd[256] = {0, };
   //::strcpy(cmd, "d:\\mencoder -oac copy -ovc copy -o d:\\aaa3.mpg d:\\2.mpg");
   ::strcpy(cmd, "e:\\ffmpeg\\trunk\\ffmpeg -i d:\\1.mpg d:\\1.avi");
   //if( !::CreateProcess(NULL, cmd, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS|CREATE_NO_WINDOW, NULL, NULL, &si, &pi) )   
   if( !::CreateProcess(NULL, cmd, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi) )   
   {      
       int errorNo = ::GetLastError();
//        AfxMessageBox("create process failed");   
       return;   
   }
   ::CloseHandle(hSTDOUTWrite);   
   ::CloseHandle(hSTDINRead);  
   char strData[1024] = {0, };
   DWORD dwBytes;
   while(::ReadFile(hSTDOUTRead, strData, sizeof(strData), &dwBytes, NULL))
   {   
       ProcessMessage();
       strData[dwBytes] = '\0';
       //SetMessage(strData);
       if(m_bCancel)
       {
           //::WriteFile(hSTDOUTWrite, "q", 2, &dwBytes, NULL);
           break;
       }
   } 
   DWORD uExitCode ;
   ::TerminateProcess(pi.hProcess,uExitCode);
   //raise(3);
   //abort();   
//    ::WaitForSingleObject(pi.hProcess, INFINITE);   
   ::CloseHandle(hSTDOUTRead);   
   ::CloseHandle(hSTDINWrite);   
   ::CloseHandle(pi.hProcess);   
   ::CloseHandle(pi.hThread);  
} 

但是用了上面的方法可是在转换文件过程中(转化结束之前),会造成程序假死现象

假死是因为主线程(进程所在线程)被阻塞,不能响应消息循环,所以就假死了,解决办法是创建一个独立的工作线程来完成ffmpeg的转换任务,与主线程通过线
程消息、信号量等进行通信或同步。
所以如果某些片子会假死, ffmpeg进程明明完成转换,可 WaitForSingleObject(ProcessInfor.hProcess, 10) == WAIT_OBJECT_0, 总是等不到进程结
束信号,ffmpeg.exe进程始终存在,不用管道或者换某些其它片子就ok了,看来像微软的bug.
可采用以下方式解决:
source code:
       HANDLE        hReadPipe;
       HANDLE  hWritePipe;
       SECURITY_ATTRIBUTES Security_Attributes;
   Security_Attributes.nLength=sizeof(SECURITY_ATTRIBUTES);
       Security_Attributes.lpSecurityDescriptor=NULL;
       Security_Attributes.bInheritHandle=TRUE;
       CreatePipe(&hReadPipe, &hWritePipe, &Security_Attributes, 0);
       STARTUPINFO StartupInfo;
       GetStartupInfo(&StartupInfo);
//      StartupInfo.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; //removed for strange bug
       StartupInfo.wShowWindow = SW_HIDE;
       StartupInfo.hStdOutput = hWritePipe;
       StartupInfo.hStdError  = hWritePipe;
       StartupInfo.hStdInput  = hReadPipe;
       PROCESS_INFORMATION ProcessInfor = {0};
       if(!CreateProcess(NULL, strCmdLine.GetBuffer(0), 
               NULL,NULL,TRUE,
               CREATE_SUSPENDED,NULL,NULL,
               &StartupInfo,&ProcessInfor))
       {
               MessageBox(_T("Can't run ffmpeg.exe, Check if missing."), _T("Error"));
               return FALSE;
       }                
       DWORD ProcessTime = GetTickCount();
       ResumeThread(ProcessInfor.hThread);
       m_isExitReady = FALSE;
       DWORD SizeRead;
       TCHAR OutputData[4096];
       DWORD TotalBytesAvail;
       DWORD BytesLeftThisMessage;
       MSG   msg;
       m_cProgress.SetRange(0, 1000);
       m_cProgress.SetStep(1);
#ifdef ENABLE_LOG
       CFile LogFile;//create log file if needed
       LogFile.Open("converter.log", CFile::modeCreate|CFile::modeWrite);
#endif //ENABLE_LOG
       do
       {
               if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
               {
                       TranslateMessage(&msg);
                       DispatchMessage(&msg);
               }
               if(PeekNamedPipe(hReadPipe, OutputData, 4096, &SizeRead,
                                               &TotalBytesAvail, &BytesLeftThisMessage))
               {
                       //get wanted data here.
#ifdef ENABLE_LOG
                       LogFile.Write(OutputData, SizeRead);
#endif //ENABLE_LOG
               }
               m_cProgress.StepIt();
       }
       while(WaitForSingleObject(ProcessInfor.hProcess, 10) != WAIT_OBJECT_0);
#ifdef ENABLE_LOG
       LogFile.Close();
#endif //ENABLE_LOG
       m_cProgress.SetPos(1000);
       CloseHandle(ProcessInfor.hProcess);
       CloseHandle(ProcessInfor.hThread);
       CloseHandle(hReadPipe);
       CloseHandle(hWritePipe);
       m_isExitReady = TRUE;
       ProcessTime = GetTickCount() - ProcessTime; 
       m_ProcessTime.Format("Cost %d ms", ProcessTime);
       UpdateData(FALSE);
       return CheckOutput();

有关该问题的讨论帖可参考ffmpeg工程组论坛中的相关讨论:

有关用管道从CMD窗口读FFMPEG执行信息的方法的讨论
Personal tools