1

I am trying to write a simple application to record audio on my galaxy watch using native app development in C.

So far I got to the stage when I start the app and have a button that starts and stops the recording.

For some reason I cannot get the recorder to work, it returns rerror -38, which is Internal error, in the logs I can see this:

mm_camcorder_audiorec.c: _mmcamcorder_audio_command(530) > file delete(/opt/usr/home/owner/apps_rw/org.example.audiorecorder/data/AUDIO-2019-11-24_01:15:51.3gp)

But I have no idea why it's deleted on recorder_commit.

What am I missing? Can you help?

Code for the application:

#include <app.h>
#include <stddef.h>
#include <privacy_privilege_manager.h>
// Functions for creating and managing user interface elements. Here are all the widgets,
// such as: windows, buttons, etc.
#include <Elementary.h>

//Functions for using callback for hardware Back button and for
//using extended widgets for a circle screen

#include <efl_extension.h>

#include "audiorecorder.h"

#include <recorder.h>
#include <camera.h>

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

typedef struct _UIData
{
    Evas_Object *win;
    Evas_Object *box;
    Evas_Object *icon;
    Evas_Object *button;
} UIData;

#define TEXT_BUF_SIZE 256
#define FILENAME_PREFIX "AUDIO"
#define NUMBER_OF_PRIVILEGES 3
#define START_RECORD_IMAGE_PATH "images/start-recording.jpg"
#define STOP_RECORD_IMAGE_PATH "images/stop-recording.png"

static recorder_h g_recorder;
bool permission_granted = false;
int user_clicked = 0;

static void
_state_changed_cb(recorder_state_e previous,
                  recorder_state_e current,
                  bool by_policy, void *user_data)
{
    dlog_print(DLOG_INFO, LOG_TAG,
               "_recorder_state_changed_cb (prev: %d, curr: %d)\n",
               previous,
               current);
}

static void
_recorder_recording_limit_reached_cb(
                                     recorder_recording_limit_type_e type,
                                     void *user_data)
{
    dlog_print(DLOG_DEBUG, LOG_TAG, "Recording limit reached: %d\n", type);
}

/* Check the audio recorder state */
static bool
_recorder_expect_state(recorder_h recorder,
                       recorder_state_e expected_state)
{
    recorder_state_e state;
    int error_code = recorder_get_state(recorder, &state);

    dlog_print(DLOG_INFO, LOG_TAG,
               "recorder state = %d, expected recorder state = %d, error_code = %d",
               state, expected_state, error_code);
    if (state == expected_state)
        return true;

    return false;
}

static void
_win_back_cb(void *data, Evas_Object *obj, void *event_info)
{
    UIData *ui = data;

    elm_win_lower(ui->win);
}

static void
app_get_resource(const char *edj_file_in, char *edj_path_out, int edj_path_max)
{
    char *res_path = app_get_resource_path();
    if (res_path)
    {
        snprintf(edj_path_out, edj_path_max, "%s%s", res_path, edj_file_in);
        free(res_path);
    }
}

static void
_icon_create(UIData *ui)
{
    dlog_print(DLOG_INFO, LOG_TAG, "icon create");
    char image_path[PATH_MAX] =
    { 0, };

    ui->icon = elm_image_add(ui->button);
    elm_image_resizable_set(ui->icon, EINA_TRUE, EINA_TRUE);
    app_get_resource(START_RECORD_IMAGE_PATH, image_path, (int) PATH_MAX);

    int error_code = elm_image_file_set(ui->icon, image_path, NULL);
    dlog_print(DLOG_INFO, LOG_TAG, "res path: %s error_code: %d", image_path, error_code);

    evas_object_show(ui->icon);
}

static void
_button_create(UIData *ui)
{
    dlog_print(DLOG_INFO, LOG_TAG, "button create");

    ui->button = elm_button_add(ui->box);

    _icon_create(ui);
    elm_object_style_set(ui->button, "circle");
    evas_object_size_hint_weight_set(ui->button, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
    evas_object_size_hint_align_set(ui->button, 0.5, 0.4);
    evas_object_size_hint_min_set(ui->button, ELM_SCALE_SIZE(90), ELM_SCALE_SIZE(90));
    elm_object_content_set(ui->button, ui->icon);

    evas_object_show(ui->button);
}

static void
_box_create(UIData *ui)
{
    dlog_print(DLOG_INFO, LOG_TAG, "box create");
    ui->box = elm_box_add(ui->win);

    evas_object_size_hint_weight_set(ui->box, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
    _button_create(ui);
    elm_object_content_set(ui->box, ui->button);
    elm_box_pack_end(ui->box, ui->button);
    evas_object_show(ui->box);
}

static void
_window_create(UIData *ui)
{
    dlog_print(DLOG_INFO, LOG_TAG, "window create");
    // Create and configure the main window
    ui->win = elm_win_util_standard_add(NULL, NULL);

    // Create a conformant - in this case, the main container in the application,
    // which is also the interface between the application and
    // system elements, such as a keyboard.
    _box_create(ui);

    //  Set the size of the conformant widget the same as the size of the window
    elm_win_resize_object_add(ui->win, ui->box);

    //Register the function that will be called
    //when you press the hardware Back button
    eext_object_event_callback_add(ui->win, EEXT_CALLBACK_BACK, _win_back_cb, ui);

    // /Always display the window only after the entire base
    // user interface will be displayed.
    evas_object_show(ui->win);
}

static void permission_request_cb(ppm_call_cause_e cause,
                                  ppm_request_result_e result,
                                  const char *privilege,
                                  void *user_data)
{
    dlog_print(DLOG_INFO, LOG_TAG, "callback called for privilege: %s with cause: %d, the result was: %d", privilege,
               cause,
               result);
    user_clicked++;
}

void *func(void *arg)
{
    while (user_clicked < NUMBER_OF_PRIVILEGES)
    {
        dlog_print(DLOG_INFO, LOG_TAG, "waiting for user to accept all privileges, so far accepted: %d", user_clicked);
        sleep(1);
    }
    return 0;
}

static void _button_click_cb(void *data, Evas_Object *button, void *ev)
{
    UIData *ui = data;
    int error_code = 0;
    dlog_print(DLOG_INFO, LOG_TAG, "clicked button!");
    char image_path[PATH_MAX] = { 0, };
    const char *image;
    const char *group;
    char *path = NULL;
    recorder_state_e state;

    elm_image_file_get(ui->icon, &image, &group);
    dlog_print(DLOG_INFO, LOG_TAG, "file get, image: %s, group: %s", image, group);

    if (strstr(image, "start") != NULL) {
        app_get_resource(STOP_RECORD_IMAGE_PATH, image_path, (int) PATH_MAX);
        error_code = elm_image_file_set(ui->icon, image_path, NULL);
        dlog_print(DLOG_INFO, LOG_TAG, "setting icon to stop, error code: %d", error_code);

        dlog_print(DLOG_INFO, LOG_TAG, "starting recorder");
        if (_recorder_expect_state(g_recorder, RECORDER_STATE_READY)
                || _recorder_expect_state(g_recorder, RECORDER_STATE_PAUSED))
        {
            error_code = recorder_start(g_recorder);
            dlog_print(DLOG_INFO, LOG_TAG, "starting recorder result: %d", error_code);
        }
        else
        {
            recorder_get_state(g_recorder, &state);
            dlog_print(DLOG_INFO, LOG_TAG, "Failure, recorder in wrong state = %d", state);
        }
    }
    else if(strstr(image, "stop") != NULL)
    {
        app_get_resource(START_RECORD_IMAGE_PATH, image_path, (int) PATH_MAX);
        error_code = elm_image_file_set(ui->icon, image_path, NULL);
        dlog_print(DLOG_INFO, LOG_TAG, "setting icon to start, error code: %d", error_code);


        /* Stop the recorder and save the recorded data to a file */
        if (_recorder_expect_state(g_recorder, RECORDER_STATE_RECORDING)
                || _recorder_expect_state(g_recorder, RECORDER_STATE_PAUSED))
        {
            error_code = recorder_get_filename(g_recorder, &path);
            dlog_print(DLOG_INFO, LOG_TAG, "going to save audio to file %s error code: %d", path, error_code);
            error_code = recorder_commit(g_recorder);
            if (error_code != RECORDER_ERROR_NONE)
            {
                dlog_print(DLOG_INFO, LOG_TAG, "Failure, error code = %d", error_code);
                recorder_cancel(g_recorder);
            }

        }
        else
        {
            recorder_get_state(g_recorder, &state);
            dlog_print(DLOG_INFO, LOG_TAG, "Failure, recorder in wrong state = %d", state);
        }
    }
}

static bool
_app_create_cb(void *data)
{
    UIData *ui = data;

    _window_create(ui);
    evas_object_smart_callback_add(ui->button, "clicked", _button_click_cb, ui);
    return true;
}

static void
_app_control_cb(app_control_h app_control, void *data)
{
    /* Handle the launch request. */
}

static void
_app_pause_cb(void *data)
{
    /* Take necessary actions when the application becomes invisible. */
}

static void
_app_resume_cb(void *data)
{

    ppm_check_result_e privilege_results_array[NUMBER_OF_PRIVILEGES];
    const char *privilege_array[NUMBER_OF_PRIVILEGES];
    pthread_t pid;
    char *recorder_privilege = "http://tizen.org/privilege/recorder";
    char *media_storage_privilege = "http://tizen.org/privilege/mediastorage";
    char *ex_media_storage_privilege = "http://tizen.org/privilege/externalstorage";

    privilege_array[0] = malloc(strlen(recorder_privilege) + 1);
    privilege_array[1] = malloc(strlen(media_storage_privilege) + 1);
    privilege_array[2] = malloc(strlen(ex_media_storage_privilege) + 1);

    strcpy((char*) privilege_array[0], recorder_privilege);
    strcpy((char*) privilege_array[1], media_storage_privilege);
    strcpy((char*) privilege_array[2], ex_media_storage_privilege);

    int result = ppm_check_permissions(privilege_array, NUMBER_OF_PRIVILEGES, privilege_results_array);
    dlog_print(DLOG_INFO, LOG_TAG, "checking permission for recorder. Result: %d", result);
    for (int i = 0; i < NUMBER_OF_PRIVILEGES; i++)
    {
        switch (privilege_results_array[i])
        {
        case PRIVACY_PRIVILEGE_MANAGER_CHECK_RESULT_ALLOW:
            dlog_print(DLOG_INFO, LOG_TAG, "Privilege: %s, Allowed!", privilege_array[i]);
            break;
        case PRIVACY_PRIVILEGE_MANAGER_CHECK_RESULT_DENY:
            dlog_print(DLOG_INFO, LOG_TAG, "Privilege: %s, Denied!", privilege_array[i]);
            break;
        case PRIVACY_PRIVILEGE_MANAGER_CHECK_RESULT_ASK:
            dlog_print(DLOG_INFO, LOG_TAG, "Privilege: %s, Gotta ask for this privilege!", privilege_array[i]);
            ppm_request_permission(privilege_array[i], permission_request_cb, NULL);
            break;
        }
    }
    pthread_create(&pid, NULL, func, NULL);

}

static void _app_terminate_cb(void *data)
{
    UIData *ui = data;

    //Before closing the application, delete the main widget (window),
    // then all  "children" widgets of this window (all widgets of this application) will be deleted.
    evas_object_del(ui->win);

    /* Release all resources. */
    int error_code = recorder_unset_recording_limit_reached_cb(g_recorder);
    error_code = recorder_unprepare(g_recorder);
    error_code = recorder_unset_state_changed_cb(g_recorder);
    error_code = recorder_destroy(g_recorder);
}

int main(int argc, char *argv[])
{

    char filename[256] =
    { '\0' };
    struct tm localtime =
    { 0 };
    time_t rawtime = time(NULL);
    int ret = 0;
    size_t size = 0;

    dlog_print(DLOG_INFO, LOG_TAG, "main");
    UIData ui =
    { 0, };
    ui_app_lifecycle_callback_s lifecycle_callbacks =
    { 0, };

    /* Create the audio recorder handle */
    int error_code = recorder_create_audiorecorder(&g_recorder);
    if (error_code == RECORDER_ERROR_NONE)
        dlog_print(DLOG_INFO, LOG_TAG, "successfully created audiorecorder error code = %d", error_code);
    else
        dlog_print(DLOG_ERROR, LOG_TAG, "Failure creating audiorecorder error code = %d", error_code);

    error_code = recorder_set_state_changed_cb(g_recorder, _state_changed_cb, NULL);
    dlog_print(DLOG_INFO, LOG_TAG, "setting recorder_set_state_changed_cb, error code = %d", error_code);

    /* Set the audio encoder */
    error_code = recorder_set_audio_encoder(g_recorder, RECORDER_AUDIO_CODEC_AAC);
    dlog_print(DLOG_INFO, LOG_TAG, "setting recorder_set_audio_encoder, error code = %d", error_code);

    /* Set video encoder */
    error_code = recorder_set_video_encoder(g_recorder, RECORDER_VIDEO_CODEC_MPEG4);
    dlog_print(DLOG_INFO, LOG_TAG, "setting recorder_set_video_encoder, error code = %d", error_code);

    /* Set the maximum file size to unlimited 10240 (kB) - 10 mb */
    error_code = recorder_attr_set_size_limit(g_recorder, 10240);
    dlog_print(DLOG_INFO, LOG_TAG, "setting recorder_attr_set_size_limit, error code = %d", error_code);

    /* Set the audio encoder bitrate */
    error_code = recorder_attr_set_audio_encoder_bitrate(g_recorder, 28800);

    dlog_print(DLOG_INFO, LOG_TAG, "setting recorder_attr_set_audio_encoder_bitrate, error code = %d", error_code);

    /* Set the audio device to microphone */
    error_code = recorder_attr_set_audio_device(g_recorder, RECORDER_AUDIO_DEVICE_MIC);
    dlog_print(DLOG_INFO, LOG_TAG, "setting recorder_attr_set_audio_device, error code = %d", error_code);

    /* Set the audio sample rate */
    error_code = recorder_attr_set_audio_samplerate(g_recorder, 44100);
    dlog_print(DLOG_INFO, LOG_TAG, "setting recorder_attr_set_audio_samplerate, error code = %d", error_code);

    /* Set the file format according to the audio encoder */
    error_code = recorder_set_file_format(g_recorder, RECORDER_FILE_FORMAT_3GP);

    dlog_print(DLOG_INFO, LOG_TAG, "setting recorder_set_file_format, error code = %d", error_code);

    /* Create the file name */
    dlog_print(DLOG_INFO, LOG_TAG, "data path = %s", app_get_data_path());

    if (localtime_r(&rawtime, &localtime) != NULL)
    {
        size = snprintf(filename, sizeof(filename),
                        "%s%s-%04i-%02i-%02i_%02i:%02i:%02i.3gp",
                        app_get_data_path(),
                        FILENAME_PREFIX,
                        localtime.tm_year + 1900, localtime.tm_mon + 1,
                        localtime.tm_mday,
                        localtime.tm_hour, localtime.tm_min,
                        localtime.tm_sec);
    }
    else
    {
        dlog_print(DLOG_INFO, LOG_TAG, "failed to save audio in a file");
    }

    /* Set the full path and file name */
    /* Set the file name according to the file format */
    error_code = recorder_set_filename(g_recorder, filename);
    dlog_print(DLOG_INFO, LOG_TAG, "setting file name to: %s, error code = %d",
               filename,
               error_code);

    error_code = recorder_set_recording_limit_reached_cb(g_recorder,
                                                         _recorder_recording_limit_reached_cb,
                                                         NULL);
    dlog_print(DLOG_INFO, LOG_TAG, "setting limit reached callback, error code = %d", error_code);

    dlog_print(DLOG_INFO, LOG_TAG, "preparing recorder");

    error_code = recorder_prepare(g_recorder);
    dlog_print(DLOG_INFO, LOG_TAG, "preparing recorder result: %d", error_code);

    lifecycle_callbacks.create = _app_create_cb;
    lifecycle_callbacks.terminate = _app_terminate_cb;
    lifecycle_callbacks.pause = _app_pause_cb;
    lifecycle_callbacks.resume = _app_resume_cb;
    lifecycle_callbacks.app_control = _app_control_cb;

    ret = ui_app_main(argc, argv, &lifecycle_callbacks, &ui);
    if (ret != APP_ERROR_NONE)
    {
        dlog_print(DLOG_ERROR, LOG_TAG, "watch_app_main() is failed. err = %d",
                   ret);
    }

    return ret;
}
Meraj al Maksud
  • 1,528
  • 2
  • 22
  • 36
Helban
  • 81
  • 12

3 Answers3

0

In your code, recorder_cancel is called if recorder_commit returns error.

error_code = recorder_commit(g_recorder);
if (error_code != RECORDER_ERROR_NONE)
{
    dlog_print(DLOG_INFO, LOG_TAG, "Failure, error code = %d", error_code);
    recorder_cancel(g_recorder);
}

So, we need to check why recorder_commit is failed. Could you show all log since recorder_prepare is called especially with MM_CAMCORDER and GST_LOG log tags?

Note: You can get runtime error with recorder_set_error_cb(). It could help you to debug error.

  • Hi, I've registered such a callback, but that didn't get called when the error occurs :( I've checked the logs, but lack of inneworkings knowledge doesnt tell me anything interesting, hopefully it'll be more for you! the logs from the moment I am doing commit are totally not helpful: [link](https://pastebin.com/ACpF6TkA) and here are the full logs with reproduction starting from the application startup: (pastebin timesout for such big log O.o - the link here is valid for 6 days) [link](https://privatebin.net/?f01ecbac72aa29d7#HaWShuagrdPrddJfammiT3nq6SXGzJ9Eip4iEq85NFGB) – Helban Nov 27 '19 at 21:59
0

Thank you for the log. I have checked it and found that the recorder_commit() was failed because the recorded file size is zero.(I can assume it with time interval of recorder_commit() function call.)

According to log, the path for recording is "/opt/usr/home/owner/apps_rw/org.example.audiorecorder/data/" which is got by app_get_data_path(). But in general, media files are not placed there. So, I suggest to change the recording file path.

Please try it with another path which can be got by storage_foreach_device_supported(). (https://developer.tizen.org/development/api-references/native-application?redirect=https://developer.tizen.org/dev-guide/4.0.0/org.tizen.native.wearable.apireference/group__CAPI__SYSTEM__STORAGE__MODULE.html#ga8f5d871bbc2d8942fe6b725c8f8fc779)

And you can check recording status with recorder_set_recording_status_cb(). If it's not called repeatably, we can assume some problem is occurred. Otherwise, we can assume it works well.

  • Hi, I've tried what you suggested, the result was the same unfortunatelly :( I've added 2 callbacks: for the storage to get the file path, that works and gives a different filepath which i use to set the recorder filename, and the one for recorder status - that one doesnt get called at all. Logs from the run: [link](https://privatebin.net/?f1f7cf5f3d990321#FCxNRZPMGRSAFpN9yphuJqeyhaJtgujqiEnFXaENJdak) – Helban Dec 01 '19 at 13:01
  • Also, the file is never created on the emulator device, I cannot see the file in the device manager browsing through folders - is that because of the commit fail? – Helban Dec 01 '19 at 13:05
0

If recording status callback is not called, it means recorded file will not be created and there is a problem in audiosrc or encoding part of recorder framework. Could you send me the your test tpk file?(jm80.yang@outlook.com) I have tested my recording testsuite with same settings(samplerate:16000, bitrate:28800, audio codec:AAC, file format:3GP) on galaxy watch, but it works fine. So I need to check the issue with test tpk which you made.

  • Here is the tkp file [link](https://file.io/gvSQ15) in the logs I can see that the callback is set properly for the recording status, I've found the file is saved under `/opt/usr/home/owner/media/Documents/` actually, however when trying to play it, it's corrupted, thats probably because `recorder_commit` gives error `Function not implemented`? – Helban Dec 03 '19 at 16:05
  • Ok!! I've decided to give the application a test on actual real device and eureka! It works there! the recorder status callback is called lots of times, and the file is saved and plays nicely! Why doesnt it work on the emulator tho? – Helban Dec 03 '19 at 17:00
  • Great! But, I have tested audio recording APIs on my PC emulator environment, it works well, too. :( The problem could be occurred on emulator by PC compatibility issue. I think you may have it unfortunately.(maybe.. MIC emulation? You can test it after remove real MIC device if it's connected to PC and reboot emulator.) Please post it if you face any other issue on Tizen development. Thank you! – Jeongmo Yang Dec 04 '19 at 09:08