2014-05-18 6 views
0

Xuggler로 스크린 레코딩 응용 프로그램을 만들고 있습니다. 기본적으로 Java 코드 괴짜의 Xuggler 자습서 코드를 실제 녹음을위한 실행 가능한 클래스에 캡슐화했습니다. 튜토리얼처럼 실행해야하지만, 실제로는 몇 가지 오류가 발생합니다. 코드 링크는 JavaCodeGeeks입니다. 이 코드 블록 전체에 대해 신용을 얻으려고하지는 않습니다.Xuggler "헤더를 쓸 수 없습니다"오류가 발생했습니다.

import java.awt.AWTException; 
import java.awt.Dimension; 
import java.awt.Rectangle; 
import java.awt.Robot; 
import java.awt.Toolkit; 
import java.awt.image.BufferedImage; 
import java.io.File; 
import java.util.concurrent.TimeUnit; 

import javax.swing.JOptionPane; 

import src.dtf.gui.GUI; 

import com.xuggle.mediatool.IMediaWriter; 
import com.xuggle.mediatool.ToolFactory; 
import com.xuggle.xuggler.ICodec; 

public class ScreenRecorder implements Runnable { 

//Booleans to run and to pause. (Pausing not implemented yet) 
boolean running = true; 
boolean paused = false; 

//Some variables 
private GUI gui; 
private Toolkit tk; 
private String path, name, outputFilename; 
private int fps; 
private long startTime; 
private Rectangle recArea; 
private Dimension bounds; 

//Declare the MediaWriter 
private IMediaWriter writer; 

//Constructor 
public ScreenRecorder(GUI gui) { 
    //Set the GUI to the one that I'm using (Another class 
    this.gui = gui; 
    //Initialize variables, based on previous user input. 
    tk = Toolkit.getDefaultToolkit(); 
    path = gui.getPath(); 
    name = JOptionPane.showInputDialog("Please enter a name for your video file:"); 
    outputFilename = path + "\\" + name + ".mp4"; 
    fps = gui.getFPS(); 
    if (gui.fullscreenChecked()) { 
     recArea = new Rectangle(0, 0, tk.getScreenSize().width, 
       tk.getScreenSize().height); 
    } else { 
     recArea = gui.getArea(); 
    } 
    bounds = new Dimension(recArea.width, recArea.height); 
} 

//Start method 
public void start() { 
    gui.disableButtons(); 
    gui.changeRecordButton(false); 
    running = true; 
} 

//Run method 
public void run() { 
    //Initialize 
    init(); 
    long lastTime = System.currentTimeMillis(); 
    int updateTime = 1000/fps; 
    startTime = System.nanoTime(); 
    while (running) { 
     //Limit updates 
     if (System.currentTimeMillis() - lastTime >= updateTime) { 
      //Ensure the recording is not paused 
      if (!paused) { 
       //If the user has stopped, stop 
       if (!gui.isRecording()) { 
        stop(); 
       } 
       //Take a screenshot and convert it 
       BufferedImage frame = takeScreenshot(); 
       BufferedImage bgrScreen = convertImage(frame, BufferedImage.TYPE_3BYTE_BGR); 
       //Encode video 
       writer.encodeVideo(0, bgrScreen, System.nanoTime() - startTime, TimeUnit.NANOSECONDS); 
       System.out.println("Recording..."); 
      } else if (paused) { 
       System.out.println("Paused..."); 
      } 
     } 
    } 
} 


private void init() { 
    //Make sure the given directory exists 
    checkFile(); 
    //Ensure there is not already a file of the same name 
    checkFilename(); 
    //Make the writer 
    writer = ToolFactory.makeWriter(outputFilename); 
    writer.addVideoStream(0, 0, ICodec.ID.CODEC_ID_MPEG4, bounds.width, bounds.height); 
} 

//Method for checking if the directory exists 
private void checkFile() { 
    if (!(new File(path).exists())) { 
     gui.resetPath(); 
     JOptionPane.showMessageDialog(gui, "ERROR: File path does not exist!"); 
     System.out.println("ERRR"); 
     stop(); 
    } 
} 

//Method for checking if the given filename exists 
private void checkFilename() { 
    if (new File(path + "\\" + name + ".mp4").exists()) { 
     JOptionPane.showMessageDialog(gui, "ERROR: File already exists!!"); 
     stop(); 
    } 
} 

//Method for converting the BufferedImage (Thanks JavaCodeGeeks) 
private BufferedImage convertImage(BufferedImage b, int targetType) { 
    BufferedImage image; 
    if (b.getType() == targetType) { 
     image = b; 
    } else { 
     image = new BufferedImage(b.getWidth(), b.getHeight(), targetType); 
     image.getGraphics().drawImage(b, 0, 0, null); 
    } 
    return image; 
} 

//Method for taking a screenshot 
private BufferedImage takeScreenshot() { 
    try { 
     Robot r = new Robot(); 
     return r.createScreenCapture(recArea); 
    } catch (AWTException e) { 
     e.printStackTrace(); 
     return null; 
    } 
} 

//Stop method 
public void stop() { 
    gui.enableButtons(); 
    gui.changeRecordButton(true); 
    //Make sure the writer has been initialized. (Not an incorrect filename or anything) 
    if (writer != null) { 
     //Close the writer 
     writer.close(); 
    } 
    //End thread 
    running = false; 
} 

} 

을 그리고 여기에 던진 것 오류입니다 : 여기

는 내가 지금까지 무엇을 가지고

17:46:48.076 [Thread-2] ERROR org.ffmpeg - [mp4 @ 000000000028F660] no streams 
17:46:48.123 [Thread-2] ERROR com.xuggle.xuggler - Error: could not write header for container (../../../../../../../csrc/com/xuggle/xuggler/Container.cpp:827) 

나는 경우 isHeaderWritten()를 추가하여 그것을 해결하기 위해 노력 정지 방법 문 ,하지만 결코 전혀 호출되지 않습니다, 그래서 그것은 어딘가에 (또는 if 문 내에서) 있어야합니다. 내 코드의 어떤 줄이 오류를 던지는지 모르겠다. 왜냐하면이 두 가지 오류는 내 코드가 아닌 Xuggler를 가리킨다. 이것을 실행하면 mp4 파일이 생성되지만 크기는 0 바이트이므로 파일이 재생되지 않습니다. 이러한 오류가 무엇을 의미하는지 모르기 때문에 실제로 도움을받을 수 있습니다. 따라서 디버깅하기가 어렵습니다. 감사!

+0

예고편 작성을 사용 중지하면 출력 파일은 어떻게됩니까? 그 내용은 여전히 ​​0 바이트입니까? 그렇다면 실제로 오류를 발생시키는 트레일러를 작성하는 경우에도 인코딩에 문제가 있습니다. IStreamCoder의 너비와 높이가 개별 이미지의 너비와 높이와 다른 것으로 설정되어 있음을 알았습니다. 그것은 그것과 관련이있을 수 있습니다. – Luke

+0

이 오류와 관련없는 부수적 인 메모입니다. 캡처 한 이미지에 버퍼를 사용하고 쓰기 위해 별도의 스레드를 사용하려고 할 것입니다. 정말 빠른 컴퓨터를 사용하지 않는 한 캡처, 변환 및 인코딩 작업은 정말 오래 걸릴 것입니다. 그리고 현재 코드가 그 시간 동안 새로운 이미지를 캡처하지 않기 때문에 프레임 속도가 다양 해지고 종종 사라집니다. 비디오 플레이어는 마음에 들지 않습니다. – Luke

+0

죄송합니다. 어디에서 예고편을 사용할 수 없나요? IMediaWriter 또는 IContainer에 있습니까? 내 코드를 편집 했으므로 이제는 bounds.width와 height를 나누는 대신 사용합니다. –

답변

0

Q &이 웹 사이트의 A- 형식을 기쁘게 생각하지 않을 경우 문제가 발생할 수 있으므로 시도해 볼 수있는 몇 가지 사항을 나열 해보겠습니다.

  • 특별히 writeHeader()을 호출하지 않으면 헤더 관련 오류가 발생합니다. '누락 된 스트림'에 관한 오류도 표시됩니다. 이것은 Xuggler가 비디오 스트림을 올바르게 추가하고 작성자를 여는 데 필요한 몇 가지 정보가 누락되었음을 나타냅니다. 그래서 자바 애플리케이션을 디버깅하여 어떤 특정 라인이 오류를 일으키는 지 파악하십시오.

  • 또한 출력하기 전에 각 출력 프레임을 JFrame으로 렌더링 해보십시오. 이렇게하면 BufferedImages에 쓰기 원하는 콘텐츠가 있는지 여부를 확인할 수 있습니다.

  • 당신이 쓰는 파일은 MP4입니다. 그러면 Xuggler가 원하는 출력 매개 변수에 대해 몇 가지 결론을 내릴 수 있지만, 그 방법에 의존하지 않는 것이 가장 좋습니다. 픽셀 형식, 비트 전송률, 프레임 속도시간대을 직접 설정해보십시오. 버퍼를 사용하는 것이 좋습니다. 적절한 간격으로 프레임을 쓸 수 있으므로 적절한 프레임 속도가 보장됩니다. 지금 설정 한 방식에 따라 가변 프레임 속도가 발생하며 일부 코덱 및 컨테이너는이를 인식하지 못합니다. 버퍼 유형은 관련이 없으므로 ArrayList을 사용할 수도 있습니다. 그러나 자연스럽게 일부 데이터 구조는 다른 것보다 효율적입니다.

  • 일부 코덱과 파일 컨테이너는 다른 것보다 훨씬 관대합니다. H.264와 같은 다른 코덱을 사용해보십시오. 파일 컨테이너를 변경해 볼 수도 있지만 MP4는 일반적으로 Xuggler에서 잘 작동합니다.

  • 이것은 출력 파일이 비어있는 것과 아무 관련이 없기 때문에 현재 문제와 관련이 없습니다. 그러나 프레임을 작성하는 타임 스탬프는주의해야합니다. 첫 번째 비디오 프레임은 타임 스탬프 0에 있어야하지만 동일한 while -loop으로 캡처하고 인코딩하므로 첫 번째 프레임의 타임 스탬프가 훨씬 높아집니다. 또한 일시 중지하면 응용 프로그램에서 프레임을 쓰지 않지만 타임 스탬프가 계속 증가합니다.이렇게하면 나중에 녹화를 다시 시작할 때 비디오에 '구멍'이 생기며 비디오 데이터가없는 작은 시간대가됩니다.

+1

흠. 이미지를 하나의 프레임으로 가져 왔고 작동하는 것 같습니다. 나는 코덱을 H264로 바꾸었고, 이제 오류가 발생한다 :'오류 org.ffmpeg - [libx264 @ 000000000010DB10] 높이가 2로 나눌 수 없다 (394x293)'및'오류 : 코덱을 열 수 없음 '. 그 정보를 직접 입력하는 방법을 모르겠습니다. 저그 러 (Xuggler)에서 내가 어떻게해야하는지 연구 좀 해줄 수 있니? 내가 본 Xuggler에 대한 문서는 매우 가난하고 대부분 컴퓨터에서로드되지 않습니다. –

+1

당신은 옳습니다. 문서는 끔찍합니다. 오류 메시지에서 Googling을 (를) 제외하고 시도 및 오류 만 수행 할 수있는 유일한 방법입니다. "2로 나눌 수없는 높이"- 오류가 쉽게 해결됩니다 : 높이 294가 293이 아니기 때문에 2로 나눌 수 있습니다. 'IMediaWriter'는 기본 'IStream'을 가지고 있으며 기본 'IStreamCoder'. 이러한 객체에 액세스 할 수있는'getStreamCoder()'와 같은 함수가 있습니다. 그런 다음'setBitRate'와 같은 함수를 사용할 수 있습니다. – Luke

+0

이제 짝수 오류를 해결했지만 IStream을 찾는 데 문제가 있습니다. 'getStreamCoder()'함수는 어디에 있습니까? 당신이 말한 것처럼 다중 스레드를 설정하고 싶지만 while 루프를 사용하지 않고 스레드를 실행하는 방법을 모르겠습니다. 아니면 버퍼가 "알려주는"경우에만 업데이트 할 while 루프를 만드는 것과 같은 것을 제안 했습니까? –