|
|
|
|
|
|
|
|
package com.example.greenwatch; |
|
|
package com.example.greenwatch; |
|
|
|
|
|
|
|
|
|
|
|
import android.Manifest; |
|
|
|
|
|
import android.content.pm.PackageManager; |
|
|
|
|
|
import android.media.MediaCodec; |
|
|
|
|
|
import android.media.MediaExtractor; |
|
|
|
|
|
import android.media.MediaFormat; |
|
|
|
|
|
import android.media.MediaMuxer; |
|
|
|
|
|
import android.media.MediaRecorder; |
|
|
|
|
|
import android.os.Bundle; |
|
|
|
|
|
import android.os.Environment; |
|
|
|
|
|
import android.view.View; |
|
|
|
|
|
import android.widget.Button; |
|
|
|
|
|
import android.widget.Toast; |
|
|
|
|
|
import android.widget.VideoView; |
|
|
|
|
|
|
|
|
import androidx.appcompat.app.AppCompatActivity; |
|
|
import androidx.appcompat.app.AppCompatActivity; |
|
|
|
|
|
import androidx.core.app.ActivityCompat; |
|
|
|
|
|
import androidx.core.content.ContextCompat; |
|
|
|
|
|
|
|
|
import android.os.Bundle; |
|
|
|
|
|
|
|
|
import java.io.File; |
|
|
|
|
|
import java.io.IOException; |
|
|
|
|
|
import java.nio.ByteBuffer; |
|
|
|
|
|
|
|
|
public class MainActivity extends AppCompatActivity { |
|
|
public class MainActivity extends AppCompatActivity { |
|
|
|
|
|
|
|
|
|
|
|
private boolean alarm_on = false; //ToDo diese Variable durch die Alarm-Variable=true setzen und nicht über einen Button |
|
|
|
|
|
private boolean alarm_off = false; //ToDo diese Variable durch die Alarm-Variable=false setzen und nicht über einen Button |
|
|
|
|
|
private static final int REQUEST_PERMISSION = 200; |
|
|
|
|
|
private Button button; |
|
|
|
|
|
private VideoView videoView; |
|
|
|
|
|
private MediaRecorder mediaRecorder; |
|
|
|
|
|
private MediaRecorder audioRecorder; |
|
|
|
|
|
private MediaFormat videoFormat; |
|
|
|
|
|
private MediaFormat audioFormat; |
|
|
|
|
|
private MediaExtractor videoExtractor; |
|
|
|
|
|
private MediaExtractor audioExtractor; |
|
|
|
|
|
private String videoPath; |
|
|
|
|
|
private String audioPath; |
|
|
|
|
|
private boolean isRecording = false; |
|
|
|
|
|
|
|
|
|
|
|
private Thread videoThread; // Video-Thread als Instanzvariable |
|
|
|
|
|
private Thread audioThread; // Audio-Thread als Instanzvariable |
|
|
|
|
|
|
|
|
|
|
|
private static final int REQUEST_PERMISSIONS = 123; |
|
|
|
|
|
|
|
|
@Override |
|
|
@Override |
|
|
protected void onCreate(Bundle savedInstanceState) { |
|
|
protected void onCreate(Bundle savedInstanceState) { |
|
|
super.onCreate(savedInstanceState); |
|
|
super.onCreate(savedInstanceState); |
|
|
setContentView(R.layout.activity_main); |
|
|
setContentView(R.layout.activity_main); |
|
|
|
|
|
|
|
|
|
|
|
button = findViewById(R.id.button); |
|
|
|
|
|
videoView = findViewById(R.id.videoView); |
|
|
|
|
|
|
|
|
|
|
|
button.setOnClickListener(new View.OnClickListener() { |
|
|
|
|
|
@Override |
|
|
|
|
|
public void onClick(View v) { |
|
|
|
|
|
if (!isRecording) { |
|
|
|
|
|
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) |
|
|
|
|
|
!= PackageManager.PERMISSION_GRANTED |
|
|
|
|
|
|| ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) |
|
|
|
|
|
!= PackageManager.PERMISSION_GRANTED |
|
|
|
|
|
|| ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.RECORD_AUDIO) |
|
|
|
|
|
!= PackageManager.PERMISSION_GRANTED) { |
|
|
|
|
|
ActivityCompat.requestPermissions(MainActivity.this, |
|
|
|
|
|
new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO}, |
|
|
|
|
|
REQUEST_PERMISSION); |
|
|
|
|
|
} else { |
|
|
|
|
|
alarm_on = true; |
|
|
|
|
|
onAlarmStateChanged(); |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
alarm_off = true; |
|
|
|
|
|
onAlarmStateChanged(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public void onAlarmStateChanged() { |
|
|
|
|
|
runOnUiThread(new Runnable() { |
|
|
|
|
|
@Override |
|
|
|
|
|
public void run() { |
|
|
|
|
|
if (alarm_on) { |
|
|
|
|
|
alarm_on = false; //Variable zurücksetzen |
|
|
|
|
|
startRecording(); |
|
|
|
|
|
} else if (alarm_off) { |
|
|
|
|
|
alarm_off = false; //Variable zurücksetzen |
|
|
|
|
|
stopRecording(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private void cache(){ |
|
|
|
|
|
//Pfade zum Zwischenspeichern der aufgenommenen Audio und Video-Datei |
|
|
|
|
|
isRecording = true; |
|
|
|
|
|
String externalStorageDirectory = Environment.getExternalStorageDirectory().getAbsolutePath(); |
|
|
|
|
|
String dcimDirectory = externalStorageDirectory + "/DCIM"; |
|
|
|
|
|
videoPath = dcimDirectory + "/video.mp4"; |
|
|
|
|
|
audioPath = dcimDirectory + "/audio.mp3"; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private void VideoThread(){ |
|
|
|
|
|
videoThread = new Thread(new Runnable() { |
|
|
|
|
|
@Override |
|
|
|
|
|
public void run() { |
|
|
|
|
|
mediaRecorder = new MediaRecorder(); |
|
|
|
|
|
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); |
|
|
|
|
|
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); |
|
|
|
|
|
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT); |
|
|
|
|
|
mediaRecorder.setOutputFile(videoPath); |
|
|
|
|
|
mediaRecorder.setOrientationHint(90); |
|
|
|
|
|
mediaRecorder.setPreviewDisplay(videoView.getHolder().getSurface()); |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
mediaRecorder.prepare(); |
|
|
|
|
|
mediaRecorder.start(); |
|
|
|
|
|
runOnUiThread(new Runnable() { |
|
|
|
|
|
@Override |
|
|
|
|
|
public void run() { |
|
|
|
|
|
Toast.makeText(MainActivity.this, "Videoaufzeichnung gestartet", Toast.LENGTH_SHORT).show(); |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
} catch (IOException e) { |
|
|
|
|
|
e.printStackTrace(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private void AudioThread(){ |
|
|
|
|
|
audioThread = new Thread(new Runnable() { |
|
|
|
|
|
@Override |
|
|
|
|
|
public void run() { |
|
|
|
|
|
audioRecorder = new MediaRecorder(); |
|
|
|
|
|
audioRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT); |
|
|
|
|
|
audioRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); |
|
|
|
|
|
audioRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); |
|
|
|
|
|
audioRecorder.setOutputFile(audioPath); |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
audioRecorder.prepare(); |
|
|
|
|
|
audioRecorder.start(); |
|
|
|
|
|
runOnUiThread(new Runnable() { |
|
|
|
|
|
@Override |
|
|
|
|
|
public void run() { |
|
|
|
|
|
Toast.makeText(MainActivity.this, "Audioaufzeichnung gestartet", Toast.LENGTH_SHORT).show(); |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
} catch (IOException e) { |
|
|
|
|
|
e.printStackTrace(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private void startRecording() { |
|
|
|
|
|
cache(); //Speicherort und -namen für Audio- und Video-Datei |
|
|
|
|
|
|
|
|
|
|
|
VideoThread(); //Videoaufzeichnungs-Thread anlegen |
|
|
|
|
|
AudioThread(); //Audioaufzeichnungs-Thread anlegen |
|
|
|
|
|
|
|
|
|
|
|
//Threads starten |
|
|
|
|
|
videoThread.start(); |
|
|
|
|
|
audioThread.start(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private void stopVideoRecording(){ |
|
|
|
|
|
if (mediaRecorder != null) { |
|
|
|
|
|
mediaRecorder.stop(); |
|
|
|
|
|
mediaRecorder.release(); |
|
|
|
|
|
mediaRecorder = null; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private void stopAudioRecording(){ |
|
|
|
|
|
if (audioRecorder != null) { |
|
|
|
|
|
audioRecorder.stop(); |
|
|
|
|
|
audioRecorder.release(); |
|
|
|
|
|
audioRecorder = null; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private void waitTillThreadsStopped(){ |
|
|
|
|
|
try { |
|
|
|
|
|
videoThread.join(); |
|
|
|
|
|
audioThread.join(); |
|
|
|
|
|
} catch (InterruptedException e) { |
|
|
|
|
|
e.printStackTrace(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private void stopRecording() { |
|
|
|
|
|
isRecording = false; |
|
|
|
|
|
try { |
|
|
|
|
|
stopVideoRecording(); |
|
|
|
|
|
stopAudioRecording(); |
|
|
|
|
|
|
|
|
|
|
|
runOnUiThread(new Runnable() { |
|
|
|
|
|
@Override |
|
|
|
|
|
public void run() { |
|
|
|
|
|
Toast.makeText(MainActivity.this, "Video- und Audioaufzeichnung beendet", Toast.LENGTH_SHORT).show(); |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
waitTillThreadsStopped(); |
|
|
|
|
|
File videoFile = new File(videoPath); //Speichere das aufgenommene Video |
|
|
|
|
|
File audioFile = new File(audioPath); //Speichere die aufgenommene Audio |
|
|
|
|
|
|
|
|
|
|
|
if (videoFile.exists() && audioFile.exists()) { |
|
|
|
|
|
//Wenn Video- und Audioaufzeichnung gestoppt und abgespeichert sind, beginne mit dem Mergeprozess der beiden |
|
|
|
|
|
mergeVideoWithAudio(); |
|
|
|
|
|
} else { |
|
|
|
|
|
Toast.makeText(MainActivity.this, "Dateien wurden nicht gefunden!", Toast.LENGTH_SHORT).show(); |
|
|
|
|
|
} |
|
|
|
|
|
} catch (RuntimeException stopException) { |
|
|
|
|
|
stopException.printStackTrace(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private void newMediaExtractor() { |
|
|
|
|
|
videoExtractor = new MediaExtractor(); |
|
|
|
|
|
try { |
|
|
|
|
|
videoExtractor.setDataSource(videoPath); |
|
|
|
|
|
int videoTrackIndex = getTrackIndex(videoExtractor, "video/"); |
|
|
|
|
|
if (videoTrackIndex < 0) { |
|
|
|
|
|
// Video-Track nicht gefunden |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
videoExtractor.selectTrack(videoTrackIndex); |
|
|
|
|
|
videoFormat = videoExtractor.getTrackFormat(videoTrackIndex); |
|
|
|
|
|
} catch (IOException e) { |
|
|
|
|
|
throw new RuntimeException(e); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private void newAudioExtractor() { |
|
|
|
|
|
audioExtractor = new MediaExtractor(); |
|
|
|
|
|
try { |
|
|
|
|
|
audioExtractor.setDataSource(audioPath); |
|
|
|
|
|
int audioTrackIndex = getTrackIndex(audioExtractor, "audio/"); |
|
|
|
|
|
if (audioTrackIndex < 0) { |
|
|
|
|
|
// Audio-Track nicht gefunden |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
audioExtractor.selectTrack(audioTrackIndex); |
|
|
|
|
|
audioFormat = audioExtractor.getTrackFormat(audioTrackIndex); |
|
|
|
|
|
|
|
|
|
|
|
} catch (IOException e) { |
|
|
|
|
|
throw new RuntimeException(e); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private void mediaExtraction(ByteBuffer buffer, int videoTrack, MediaCodec.BufferInfo bufferInfo, MediaMuxer muxer) { |
|
|
|
|
|
videoExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC); |
|
|
|
|
|
while (true) { |
|
|
|
|
|
int sampleSize = videoExtractor.readSampleData(buffer, 0); |
|
|
|
|
|
if (sampleSize < 0) { |
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
long presentationTimeUs = videoExtractor.getSampleTime(); |
|
|
|
|
|
bufferInfo.offset = 0; |
|
|
|
|
|
bufferInfo.size = sampleSize; |
|
|
|
|
|
bufferInfo.flags = MediaCodec.BUFFER_FLAG_KEY_FRAME; |
|
|
|
|
|
bufferInfo.presentationTimeUs = presentationTimeUs; |
|
|
|
|
|
muxer.writeSampleData(videoTrack, buffer, bufferInfo); |
|
|
|
|
|
videoExtractor.advance(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private void audioExtraction(ByteBuffer buffer, int audioTrack, MediaCodec.BufferInfo bufferInfo, MediaMuxer muxer) { |
|
|
|
|
|
audioExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC); |
|
|
|
|
|
while (true) { |
|
|
|
|
|
int sampleSize = audioExtractor.readSampleData(buffer, 0); |
|
|
|
|
|
if (sampleSize < 0) { |
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
long presentationTimeUs = audioExtractor.getSampleTime(); |
|
|
|
|
|
bufferInfo.offset = 0; |
|
|
|
|
|
bufferInfo.size = sampleSize; |
|
|
|
|
|
bufferInfo.flags = 0; // or MediaCodec.BUFFER_FLAG_KEY_FRAME |
|
|
|
|
|
bufferInfo.presentationTimeUs = presentationTimeUs; |
|
|
|
|
|
muxer.writeSampleData(audioTrack, buffer, bufferInfo); |
|
|
|
|
|
audioExtractor.advance(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private void mergeVideoWithAudio() { |
|
|
|
|
|
try { |
|
|
|
|
|
newMediaExtractor(); //extrahieren der Video Datei, die zuvor zwischengespeichert wurde |
|
|
|
|
|
newAudioExtractor(); //extrahieren der Audio Datei, die zuvor zwischengespeichert wurde |
|
|
|
|
|
|
|
|
|
|
|
//Speicherort der später zusammengeführten Datei |
|
|
|
|
|
String outputFilePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES).getAbsolutePath() + "/merged_video.mp4"; |
|
|
|
|
|
//MediaMuxer zum Zusammenführen einer Audio- und einer Videodatei |
|
|
|
|
|
MediaMuxer muxer = new MediaMuxer(outputFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); |
|
|
|
|
|
int videoTrack = muxer.addTrack(videoFormat); |
|
|
|
|
|
int audioTrack = muxer.addTrack(audioFormat); |
|
|
|
|
|
muxer.start(); |
|
|
|
|
|
|
|
|
|
|
|
ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024); |
|
|
|
|
|
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); |
|
|
|
|
|
|
|
|
|
|
|
mediaExtraction(buffer, videoTrack, bufferInfo, muxer); |
|
|
|
|
|
audioExtraction(buffer, audioTrack, bufferInfo, muxer); |
|
|
|
|
|
|
|
|
|
|
|
muxer.stop(); |
|
|
|
|
|
muxer.release(); |
|
|
|
|
|
|
|
|
|
|
|
runOnUiThread(new Runnable() { |
|
|
|
|
|
@Override |
|
|
|
|
|
public void run() { |
|
|
|
|
|
Toast.makeText(MainActivity.this, "Video und Audio erfolgreich zusammengeführt", Toast.LENGTH_SHORT).show(); |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// Löschen der separaten Video- und Audio-Dateien |
|
|
|
|
|
deleteVideoFile(); |
|
|
|
|
|
deleteAudioFile(); |
|
|
|
|
|
} catch (IOException e) { |
|
|
|
|
|
e.printStackTrace(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private void deleteVideoFile(){ |
|
|
|
|
|
File videoFile = new File(videoPath); |
|
|
|
|
|
if (videoFile.exists()) { |
|
|
|
|
|
videoFile.delete(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private void deleteAudioFile(){ |
|
|
|
|
|
File audioFile = new File(audioPath); |
|
|
|
|
|
if (audioFile.exists()) { |
|
|
|
|
|
audioFile.delete(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private int getTrackIndex(MediaExtractor extractor, String mimeType) { |
|
|
|
|
|
int trackCount = extractor.getTrackCount(); |
|
|
|
|
|
for (int i = 0; i < trackCount; i++) { |
|
|
|
|
|
MediaFormat format = extractor.getTrackFormat(i); |
|
|
|
|
|
String trackMimeType = format.getString(MediaFormat.KEY_MIME); |
|
|
|
|
|
if (trackMimeType.startsWith(mimeType)) { |
|
|
|
|
|
return i; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
return -1; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |