[JAVA] vreemd audio-capture probleem

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • chronozphere
  • Registratie: Juli 2006
  • Laatst online: 16-12-2020
Hey,

Ik ben tegen een raar probleem aangelopen tijdens het ontwikkelen van mijn opname tooltje. Ik wil een simpel tooltje maken waarmee ik geluid kan opnemen en de opname kan pauzeren en hervatten. Dit doe ik door tijdens het opstarten TargetDataLine.open(...) aan te roepen. Pas als ik de opname wil starten roep ik TargetDataLine.start() aan. Dit heeft tot gevolg dat de interne buffer van TargetDataLine volstroomt met data die je m.b.v een aparte Capture-Thread uitleest en wegschrijft.

Het vreemde is dat op Ubuntu 10.4, de buffer van TargetDataLine al volloopt na de open(..) call. Ik heb om te debuggen, om de 250ms, de state van de targetDataLine geprint samen met de hoeveelheid data die in de buffer zit. Hier is de opname dus nog niet begonnen. De calls: isRunning(), isActive() en available().
sRunning() false isActive() false available: 0
isRunning() false isActive() false available: 0
isRunning() false isActive() false available: 11570
isRunning() false isActive() false available: 11570
isRunning() false isActive() false available: 11570
isRunning() false isActive() false available: 23140
isRunning() false isActive() false available: 23140
isRunning() false isActive() false available: 23140
isRunning() false isActive() false available: 34710
etc..
Je ziet dus dat de buffer al volstroomt terwijl dat nog niet zou moeten gebeuren. In windows 7 krijg ik de volgende uitvoer:
isRunning() false isActive() false available: 0
isRunning() false isActive() false available: 0
isRunning() false isActive() false available: 0
isRunning() false isActive() false available: 0
isRunning() false isActive() false available: 0
isRunning() false isActive() false available: 0
isRunning() false isActive() false available: 0
isRunning() false isActive() false available: 0
isRunning() false isActive() false available: 0
Zo hoort het natuurlijk. :)

Heb een workaround gevonden, namelijk het starten en stoppen van de TargetDataLine direct na de Open(..) call. Dus:

code:
1
2
3
4
5
6
7
8
9
      //Get everything set up for capture
      audioFormat = getAudioFormat();
      DataLine.Info dataLineInfo =  new DataLine.Info(TargetDataLine.class, audioFormat);
      captureDataLine = (TargetDataLine) AudioSystem.getLine(dataLineInfo);
      captureDataLine.open(audioFormat);

      //Workaround!
      captureDataLine.start();
      captureDataLine.stop();


En mijn volledige source:

[mods] Heb source even opgeschoond. [/mods]

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.*;
import javax.sound.sampled.*;

public class CutFrame extends JFrame {

  private boolean capturing = false;
  private boolean finishCapturing = false;

  //When audio is captured it is written to this stream
  ByteArrayOutputStream capturedAudioData;

  //The two data lines to the mixer used for capturing and playback respectively
  TargetDataLine captureDataLine;

  //Audio format used for both capture and playback
  AudioFormat audioFormat;
  
  public CutFrame() {
    //Create buttons and enable some of them
    final JButton startBtn    = new JButton("Start");
    
    //Add event handlers for buttons
    startBtn.addActionListener( new ActionListener(){
        public void actionPerformed( ActionEvent e){
          startBtn.setEnabled(false);

          //Starts capturing audio data
          System.out.printf("START CAPTURE!!\n");
          captureDataLine.start();
          capturing = true;
        }
      } );

    getContentPane().add(startBtn);
    getContentPane().setLayout( new FlowLayout() );

    //Starts the audio capture thread
    startCaptureThread();

    //Set remaining UI attributes
    setTitle("Recording audio");
    setDefaultCloseOperation( EXIT_ON_CLOSE);
    setSize(400, 70);
    setVisible(true);
  }

  //This method captures audio input
  // from a microphone and saves it in
  // a ByteArrayOutputStream object.
  private void startCaptureThread(){
    try{
      //Get everything set up for capture
      audioFormat = getAudioFormat();
      DataLine.Info dataLineInfo =  new DataLine.Info(TargetDataLine.class, audioFormat);
      captureDataLine = (TargetDataLine) AudioSystem.getLine(dataLineInfo);
      captureDataLine.open(audioFormat);

      //Solution HERE!!! 
      //captureDataLine.start();
      //captureDataLine.stop();
      //Solution HERE!!! 

      capturing       = false;
      finishCapturing = false;

      //Create and start capture thread
      Thread captureThread = new Thread( new CaptureThread());
      captureThread.start();
    } catch (Exception e) {
      System.out.println(e);
      System.exit(0);
    }
  }

  //The audio capturing thread
  class CaptureThread extends Thread{

    public void run(){

      //Create new audio data stream to write to
      capturedAudioData   = new ByteArrayOutputStream();

      //Create temporary buffer
      byte tempBuffer[] = new byte[1000]; 

      try
      {
        //Read data until capture is stopped  
        while(!finishCapturing)
        {
          //Are we capturing audio??
          if (capturing) 
          {
            //Fetch data from captureDataLine and store it into capturedAudioData
            int cnt = captureDataLine.read( tempBuffer, 0, tempBuffer.length);
            if(cnt > 0)
            {
              capturedAudioData.write(tempBuffer, 0, cnt);
            }  
          }

          //Wait some time 
          sleep(250);

          //Print status
          System.out.printf("isRunning() %b isActive() %b available: %d \n", 
                            captureDataLine.isRunning(), 
                            captureDataLine.isActive(),
                            captureDataLine.available());
        }

        //Close capture line
        captureDataLine.stop();
        captureDataLine.flush();
        captureDataLine.close();
      } catch (Exception e) {
        System.out.println(e);
        System.exit(0);
      }
    }

  }


  //Specify audio format here!
  private AudioFormat getAudioFormat(){
    float sampleRate = 8000.0F;   //can be 8000,11025,16000,22050,44100
    int sampleSizeInBits = 16;    //can be 8 or 16
    int channels = 1;             //can be 1 or 2
    boolean signed = true;
    boolean bigEndian = false;
    return new AudioFormat(sampleRate, sampleSizeInBits, channels, signed, bigEndian);
  }

  public static void main(String[] argv) {
    new CutFrame();
  }

}


Heb het gevoel dat ik hier een bug in de java-implementatie van linux heb ontdekt. Klopt dat?
Kan iemand hier nog iets zinnigs over zeggen?

En niet onbelangrijk: waar kan ik deze bug het beste reporten?

Bedankt. :)

[ Voor 218% gewijzigd door chronozphere op 27-02-2011 14:00 ]


Acties:
  • 0 Henk 'm!

  • Herko_ter_Horst
  • Registratie: November 2002
  • Niet online
Waarom zou het vollopen van de buffer een bug zijn?

En je zegt "de java-implementatie van linux". Bedoel je daarmee GNU Classpath o.i.d.? In dat geval: http://www.gnu.org/software/classpath/bugs.html

Of bedoel je "gewoon" de Linux versie van de Sun Oracle JRE? In dat geval: http://bugs.sun.com/ (had je zelf ook wel kunnen vinden).

"Any sufficiently advanced technology is indistinguishable from magic."


Acties:
  • 0 Henk 'm!

  • chronozphere
  • Registratie: Juli 2006
  • Laatst online: 16-12-2020
Omdat het vollopen van die buffer voor ongewenst gedrag zorgt. Als ik start() aanroep verwacht ik namelijk dat hij op DAT moment begint met opnemen. Als de buffer alvast vol zit met data van de tijd daarvoor dan word die data ook meegenomen en duurt de opname langer dan de bedoeling is.

Ook de eerste line in de documentatie van Start() zegt wat mij betreft genoeg:
Allows a line to engage in data I/O
Dus het lijkt me niet de bedoeling dat dit al gebeurd na Open().

Je zou zeggen, flush() dan even voordat je start() aanroept. Dat heb ik ook geprobeerd, maar flush lijkt niets uit te halen. Als ik available(), flush() en daarna nog eens available() aanroep krijg ik exact hetzelfde aantal bytes terug. Misschien dat ik hier een denkfout maak, maar het lijkt ofals flush niet doet wat hij zou moeten doen.

Allemaal erg vreemd. :?

Acties:
  • 0 Henk 'm!

  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
Ik heb de complete lap code weg gehaald, we zien hier graag alleen relevante stukken code. Dus probeer het stuk wat de problemen veroorzaakt te isoleren, daardoor is het voor andere mensen makkelijker om er doorheen de lezen, en op die manier ook makkelijker om jou te helpen.

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


Acties:
  • 0 Henk 'm!

  • chronozphere
  • Registratie: Juli 2006
  • Laatst online: 16-12-2020
Heb ongeveer 50% van de code weggesmeten. Hoop dat het nu wat duidelijker is. :)

Acties:
  • 0 Henk 'm!

  • chronozphere
  • Registratie: Juli 2006
  • Laatst online: 16-12-2020
Heb dit uiteindelijk opgelost door even naar de Sun JRE om te schakelen. :) Bleek dat OpenJDK niet goed met PulseAudio overweg kon.

Heb de volgende aanwijzingen gevolgt (niet vergeten om dit ook voor javac te doen!):

http://blog.mypapit.net/2007/10/how-to-switch-between-different-java-jvm-in-ubuntu-and-debian-gnu-linux.html
Pagina: 1