I am working on a live wallpaper that uses a AudioRecord object to listen to the phone's mic. This works fine until I start using the wallpaper and then also view the preview wallpaper in the wallpaper selection menu. When this happens, I start having problems starting and stopping the AudioRecord object. Currently this is handled by onVisibilityChanged:
#Override
public void onVisibilityChanged(boolean visible) {
if (this.visible == visible)
return;
this.visible = visible;
if (visible) {
recorder.startRecording();
handler.post(drawRunner);
} else {
recorder.stop();
handler.removeCallbacks(drawRunner);
}
}
Is there some solution to this? I have tried several things, but nothing has worked out. This solution does (rarely) work, and as far as I can tell this is because the two instances of the wallpaper engine update their visibility in an unpredictable order. Sometimes the main wallpaper sets itself to not be visible before the preview sets itself to be visible, and everything is fine. Sometimes this order is reversed and everything breaks.
So, I found an answer and it was stupidly simple. I swear I tried this already and it didn't work, but everything is working fine now. All I had to do was move the AudioRecord object outside of the engine class and into the service class. I then create the AudioRecord object in onCreateEngine only if it isn't already instantiated. That way, all engine instances use the same AudioRecord object.
Related
I am new to Camera2 framework and trying to understand the logic of creation of capture sessions.
I need a simple thing - preview and record video. I also want to set the correct orientation hint at the time I start recording the video. But I came to a chicken/egg problem.
Here is my logic:
In order to start recording, I am doing this:
val recordRequest = session.device.createCaptureRequest(CameraDevice.TEMPLATE_RECORD).apply {
// Add the preview and recording surface targets
addTarget(viewFinder.holder.surface)
addTarget(recorder.surface)
}.build()
session.setRepeatingRequest(recordRequest, null, cameraHandler)
recorder.setOrientationHint(it) // NOT allowed after getSurface()!
recorder.prepare() // NOT allowed after getSurface()!
recorder.start()
However, I already called recorder.surface (or getSurface()) when I added targets above. One can think that I can prepare and then add targets, however, the documentation for addTarget() says, that the surface The Surface added must be one of the surfaces included in the most recent call to CameraDevice#createCaptureSession
That leads to an interesting problem. Whenever I open the app, I need to create the capture session to start previewing camera image. However, at the point of creation the createCaptureSession() needs to include all the surfaces that will come in future capture requests. Which means that I also need to include the recording surface, even if I simply open camera without recording yet. How do I get this Surface for recording? Well, the documentation says I can get it from MediaRecorder or I can get it from MediaCodec. I want to get it from MediaRecorder since I want to use CamcorderProfiles. However, as I showed in the above code, once I get the surface from the recorder at the point of session creation - I cannot do any changes there at the point of starting recording, like setting orientation hint.
The official Camera2Video sample app does a trick - it uses createPersistentInputSurface however in their example the camera is fixed, which allows them to allocate enough memory for it and use that surface throughout the app lifecycle.
How can this be solved? Am I misunderstanding the concepts here? How can I create the recorder at a later point, when I start recording, but still have the surface for it created earlier, when I open the camera for preview?
Using a persistent input surface is the right approach. Create a new MediaRecorder once you know the orientation for recording, and set its Surface using the persistent input surface.
That's exactly what the Camera2Video sample does, as well:
// React to user touching the capture button
capture_button.setOnTouchListener { view, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> lifecycleScope.launch(Dispatchers.IO) {
// Prevents screen rotation during the video recording
requireActivity().requestedOrientation =
ActivityInfo.SCREEN_ORIENTATION_LOCKED
// Start recording repeating requests, which will stop the ongoing preview
// repeating requests without having to explicitly call `session.stopRepeating`
session.setRepeatingRequest(recordRequest, null, cameraHandler)
// Finalizes recorder setup and starts recording
recorder.apply {
// Sets output orientation based on current sensor value at start time
relativeOrientation.value?.let { setOrientationHint(it) }
prepare()
start()
}
and recorder is created with an earlier-created persistent surface:
/** Saves the video recording */
private val recorder: MediaRecorder by lazy { createRecorder(recorderSurface)
}
When you say the camera is fixed, do you mean the app orientation being fixed, or that the sample doesn't support switching front/back cameras? None of that should particularly matter for persistent surfaces; you can create a new one when you switch cameras or change orientations, if you need to.
My first experience with background video recording on Android was with JavaCV FFMpegRecoder. It's easy to implement, just create camera instance in activity, set PreviewCallback listener in the service, on onPreviewFrame just send byte to FFMpegRecorder and don't destroy (disconnect) camera in onPause or onStop of course
But FFMpegRecorder isn't that good (cpu, memory usage)
So I found INDExOS m4m library (by Intel): https://github.com/INDExOS/media-for-mobile
It has CameraCapturerActivity.java - https://github.com/INDExOS/media-for-mobile/blob/master/samples/src/main/java/org/m4m/samples/CameraCapturerActivity.java
Seems it really doesn't eat many resources
I decided to try recording in background mode, I just simply commented its onPause method where stop recording and preview methods are executed, but it just doesn't record anything (freezes on the last frame) until I return to activity
When I set PreviewCallback listener to this class, onPreviewFrame sends byte in background ok, seems onFrameAvailable of SurfaceTexture related to delivering frames in m4m library are stopped when onPause is called from Activity
library has two onFrameAvailable listeners:
first in PreviewRender.java - https://github.com/INDExOS/media-for-mobile/blob/master/android/src/main/java/org/m4m/android/PreviewRender.java#L241, seems class contains everything that related to displaying frames in view class (so should not be important for recording video)
second in CameraSource.java - https://github.com/INDExOS/media-for-mobile/blob/master/android/src/main/java/org/m4m/android/CameraSource.java#L222
seems this is the main class that gets frames, and I guess it is used for video recording
But also it seems those classes are still related in quite things
For example if I comment createPreview method in CameraCapturerActivity.java
private void createPreview() {
surfaceView = new GLSurfaceView(getApplicationContext());
surfaceView.setDebugFlags(GLSurfaceView.DEBUG_CHECK_GL_ERROR);
((RelativeLayout) findViewById(R.id.camera_layout)).addView(surfaceView, 0);
preview = capture.createPreview(surfaceView, camera);
preview.setFillMode(fillMode);
if (getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
capture.setOrientation(90);
} else if (getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
capture.setOrientation(0);
}
preview.start();
}
app will run ok, of course I won't see frames, but recording won't be working when trying to press the record button, so recording doesn't work if preview wasn't created
So I need help to understand how all of this works, how could I pause preview but continue recording in background when I leave activity and resume preview when returning to activity again. I didn't work with SurfaceTexture, GLSurfaceView, only worked with ordinary SurfaceView and its holder callbacks onSurfaceCreated, onSurfaceChanged,..
I just don't see in the project something similar to onSurfaceDestroy which would stop recording when user leaves activity
I see OpenGl API, textures are also used in Grafika project https://github.com/google/grafika
So I believe there are people who worked with something like this and could know how SurfaceTexture and its callbacks (onFrameAvailable,..) works
Of course many things are related to m4m library code design itself, but still hard to understand something when you didn't work with all of this (opengl, surfacetexture,...)
UPDATE
Now I know a little about EGLContext, that we have to set it to a specific source (for preview or for recording - encoder)
I succeeded to make Grafika recording example working in background https://github.com/google/grafika/blob/master/src/com/android/grafika/ContinuousCaptureActivity.java
In that sample class I commented everything in onPause, removed mDisplaySurface and did other things
And onFrameAvailable looks like this now:
#Override // SurfaceTexture.OnFrameAvailableListener; runs on arbitrary thread
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
Log.d(TAG, "frame available");
if (mEglCore == null) {
return;
}
mEncoderSurface.makeCurrent();
mCameraTexture.updateTexImage();
mCameraTexture.getTransformMatrix(mTmpMatrix);
if (!mFileSaveInProgress) {
GLES20.glViewport(0, 0, VIDEO_WIDTH, VIDEO_HEIGHT);
mFullFrameBlit.drawFrame(mTextureId, mTmpMatrix);
drawExtra(mFrameNum, VIDEO_WIDTH, VIDEO_HEIGHT);
mCircEncoder.frameAvailableSoon();
mEncoderSurface.setPresentationTime(mCameraTexture.getTimestamp());
mEncoderSurface.swapBuffers();
}
mFrameNum++;
//mHandler.sendEmptyMessage(MainHandler.MSG_FRAME_AVAILABLE);
}
So now when I press home button, it still records frames to a file I can see it later
Now I need to get back to m4m library, cause it record audio and has utils for frame processing
In Grafika, all the videos are recorded in internal storage.
Try to change the output path of video to see it in other player. Something like that:
File outputFile = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
boolean isPresent = true;
if (!outputFile.exists()) {
isPresent = outputFile.mkdir();
}
if (isPresent) {
outputFile = new File(outputFile.getAbsolutePath(),"camera-test.mp4");
} else {
// Failure
}
You can change CameraCaptureActivity (Show + camera capture) example to perform what you want. Comment OnPause method and change openCamera like that:
private void openCamera(int desiredWidth, int desiredHeight) {
if (mCamera != null) {
return;
}
I am having a headache over the Camera API 1 for android. After reading all of the Internet content, I made some sample app that works OK. It creates a service, which then is used to operate with the camera in the background, so there is no preview or activity enabled. To achieve this I use a dummy SurfaceHolder, like this:
protected class MySurfaceHolder implements SurfaceHolder {
private final Surface surface;
private final SurfaceTexture surfaceTexture;
public MySurfaceHolder () {
int[] textures = new int[1];
GLES20.glGenTextures(1, textures, 0);
if (textures.length > 0) {
this.surfaceTexture = new SurfaceTexture(textures[0]);
this.surface = new Surface(this.surfaceTexture);
} else {
this.surface = null;
this.surfaceTexture = null;
}
}
[...]
}
and then I use it like this
// simplified version of my code
try {
initializeCamera(); // open camera and set Camera.Parameters
camera.setPreviewDisplay(new MySurfaceHolder());
camera.startPreview();
camera.unlock();
initializeMediaRecorder(); // create MediaRecorder, set video/audio parameters
mediaRecorder.prepare();
mediaRecorder.start();
// wait until recording finish and exit
} finally {
stopRecording();
}
the Camera and MediaRecorder initialization methods are just like the documentation states they should be (and they work).
Everything works and operates as it should. Almost everything - sometimes, under unknown circumstances the MediaRecorder creates empty files, like 32kB containing only headers and info about the video - no frames. The longer I record like this, the bigger is the file (few kB every few seconds). After 1 minute, the file weights about 80kB. Funny thing is I know that the camera is working and capturing frames (I debugged it a little showing preview frames), but the frames are not written into the output file.
Also when it happens I am not able to record in FHD (1920x1080) - I get the "start failed" message - at this time camera is not capturing frames. The same thing could happen when I use wrong (not supported) video size. I suppose in this case the message is thrown at the mediaRecorder.start(); line, and stopRecording(); is invoked but I am not sure.
After some time or after unknown action the problem is suddenly gone (I don't know when, I don't know how). It happens for sure on Android 5.1, but may happen on other versions as well.
Could this bug be related to my custom surface code?
What could cause the MediaRecorder to not write frames into a file?
Why I am not able to record in FHD, but in the same time I am able to record in HD (1280x720)?
Is there any alternative for MediaRecorder, so I can avoid these bugs?
May it happen when another app is trying to get Camera object, thus distrupting current recording? If so, how to regain access to the Camera object (I apparently am not able to do this now on some devices).
EDIT:
I think I might have a clue. I am calling
camera.setOneShotPreviewCallback(new Camera.PreviewCallback() {
// ... get current frame
}
camera.startPreview();
to get preview frame of current recording. It appears that the bug occurs when I am using this method to get preview frame (at random times). It seems flawed, because not all devices react to this thing properly (sometimes there is no preview frame...). Is there any other, better method of handling current preview frame without the real surface?
I'm creating a simple game with AndEngine. A ball is dropped toward a floor, and whenever it collides with the floor, I want to play a short colliding sound. In method onUpdate(), I check for collision and play sound accordingly.
I use class Sound for playing sound (as in the SoundExample of AndEngine). Testing on Samsung Galaxy S2.
The problem is the program gets lagged when the sound is played. And it even affects game physics (sometimes the ball bounces higher than the highest point when disabling sound).
This is the code:
public void onUpdate(float pSecondsElapsed) {
// mSound.play();
if (this.mSprite.collidesWith(ball.getSprite())) {
if (!colliding && mSound != null){ // play sound for first collision only
mSound.play();
colliding = true;
}
}
else{
colliding = false;
}
}
If I remove mSound.play() or keep playing sound (remove comment at line 2), the program works smoothly.
Does anyone encounter the same problem? And have a solution to get rid of the lag? Many thanks!
as that you mentioned that it works smoothly when you keep playing the sound .. then the problem is not with the sound
the collidesWith() method is probably your culprit, remember that onUpdate gets called every frame .. maybe you'll have to redesign your code or limit the number of frames per second [change your engine options to use a FixedStepEngine to achieve that]
I am working on an app that will allow a user to take quick click and forget snapshots. Most of the app is done except for the camera working that way I would like. Right now I have the camera working but I can't seem to find a way to disable the shutter sound and I cant find a way to disable displaying the preview. I was able to cover the preview up with a control but I would rather just not have it displayed if possible.
To sum things up, these are the items that I would like to disable while utilizing the built in Camera controls.
Shutter sound
Camera screen display
Image preview onPictureTaken
Does anyone know of a resource that could point me in the right direction, I would greatly appreciate it. I have been following CommonsWare's example from this sample fairly closely.
Thank you.
This is actually a property in the build.prop of a phone. I'm unsure if its possible to change this. Unless you completely override it and use your own camera code. Using what you can that is available in the SDK.
Take a look at this:
CameraService.cpp
. . .
CameraService::Client::Client(const sp<CameraService>& cameraService,
const sp<ICameraClient>& cameraClient,
const sp<CameraHardwareInterface>& hardware,
int cameraId, int cameraFacing, int clientPid) {
mPreviewCallbackFlag = FRAME_CALLBACK_FLAG_NOOP;
mOrientation = getOrientation(0, mCameraFacing == CAMERA_FACING_FRONT);
mOrientationChanged = false;
cameraService->setCameraBusy(cameraId);
cameraService->loadSound();
LOG1("Client::Client X (pid %d)", callingPid)
}
void CameraService::loadSound() {
Mutex::Autolock lock(mSoundLock);
LOG1("CameraService::loadSound ref=%d", mSoundRef);
if (mSoundRef++) return;
mSoundPlayer[SOUND_SHUTTER] = newMediaPlayer("/system/media/audio/ui/camera_click.ogg");
mSoundPlayer[SOUND_RECORDING] = newMediaPlayer("/system/media/audio/ui/VideoRecord.ogg");
}
As can be noted, the click sound is started without your interaction.
This is the service used in the Gingerbread Source code.
The reason they DON'T allow this is because it is illegal is some countries. Only way to achieve what you want is to have a custom ROM.
Update
If what being said here: http://androidforums.com/t-mobile-g1/6371-camera-shutter-sound-effect-off.html
still applies, then you could write a timer that turns off the sound (Silent Mode) for a couple of seconds and then turn it back on each time you take a picture.
You may use the data from the preview callback using a function to save it at a picture on some type of trigger such as a button, using onclick listener. you could compress the image to jpeg or png. In this way, there no shutterCallback to be implemented. and therefore you can play any sound you want or none when taking a picture.
You can effectively hide the preview surface by giving it dimensions of 1p in the xml file (I found an example the said 0p but for some reason that was giving me errors).
It may be illegal to have a silent shutter in some places, but it doesn't appear that the US is such a place, as my HTC One gives me an option to silence it, and in fact, since Android 4.2 you can do this:
Camera.CameraInfo info=new Camera.CameraInfo();
if (info.canDisableShutterSound) {
camera.enableShutterSound(false);
}