반응형

Application Development Manual - Your first application을 윈도우에서 컴파일하고 실행하면 아래와 같이 에러가 발생한다.

 

 

 

어떤 엘리먼트에서 에러가 발생했는지 정확히 파악하려면 GStreamer 디버깅 로그를 켜는 것이 좋습니다. Visual Studio에서 프로그램을 실행하기 전에 다음 환경 변수를 설정하세요.
- 환경 변수 설정: Visual Studio 프로젝트 속성 - Debugging - Environment 항목에 아래 내용 추가 GST_DEBUG=3 (또는 더 상세한 로그를 원하면 GST_DEBUG=4 지정)
이렇게 하면 콘솔 출력창이나 리눅스/윈도우 터미널에 에러를 발생시킨 конкрет한 원인(예: Not Negotiated, Wrong File Type 등)이 출력되어 디버깅에 큰 도움이 됩니다.

 

조금 더 자세한 내용이 표시된다.

Internal data stream error.

streaming stopped, reason not-linked (-1)

 

#include <gst/gst.h>
#include <glib.h>

static gboolean bus_call(GstBus* bus, GstMessage* msg, gpointer data)
{
	GMainLoop* loop = (GMainLoop*)data;

	switch (GST_MESSAGE_TYPE(msg)) {
	case GST_MESSAGE_EOS:
		g_print("End of stream\n");
		g_main_loop_quit(loop);
		break;

	case GST_MESSAGE_ERROR: {
		gchar* debug;
		GError* error;

		gst_message_parse_error(msg, &error, &debug);
		g_free(debug);

		g_printerr("Error: %s\n", error->message);
		g_error_free(error);

		g_main_loop_quit(loop);
		break;
	}
	default:
		break;
	}

	return TRUE;
}

static void on_pad_added(GstElement* element, GstPad* pad, gpointer data)
{
	GstPad* sinkpad;
	GstElement* decoder = (GstElement*)data;

	// 새로 추가된 pad의 caps를 가져옴
	GstCaps* caps = gst_pad_get_current_caps(pad);
	if (!caps)
		caps = gst_pad_query_caps(pad, NULL);

	// caps의 구조체에서 이름을 가져옴
	const gchar* name = gst_structure_get_name(gst_caps_get_structure(caps, 0));
	g_print("New pad '%s' added with caps '%s'\n", GST_PAD_NAME(pad), name);

	// 오디오(vorbis) 스트림인지 확인
	if (g_str_has_prefix(name, "audio/x-vorbis")) {
		g_print("Dynamic pad created, linking demuxer/decoder\n");

		sinkpad = gst_element_get_static_pad(decoder, "sink");
		// 이미 연결되어 있는지 확인
		if (!gst_pad_is_linked(sinkpad)) {
			GstPadLinkReturn ret = gst_pad_link(pad, sinkpad);
			if (ret != GST_PAD_LINK_OK)
				g_printerr("Failed to link decoder. Error code: %d\n", ret);
			else
				g_print("Successfully linked demuxer and decoder.\n");
		}
		gst_object_unref(sinkpad);
	}
	else {
		g_print("Ignoring non-audio pad: %s\n", name);
	}
	gst_caps_unref(caps);
}

int main(int argc, char* argv[])
{
	GMainLoop* loop;
	GstElement* pipeline, * source, * demuxer, * decoder, * conv, * sink;
	GstBus* bus;
	guint bus_watch_id;

	/* Initialisation */
	gst_init(&argc, &argv);

	loop = g_main_loop_new(NULL, FALSE);

	/* Check input arguments */
	if (argc != 2) {
		g_printerr("Usage: %s <Ogg/Vorbis filename>\n", argv[0]);
		return -1;
	}

	/* Create gstreamer elements */
	pipeline = gst_pipeline_new("audio-player");
	source = gst_element_factory_make("filesrc", "file-source");
	demuxer = gst_element_factory_make("oggdemux", "ogg-demuxer");
	decoder = gst_element_factory_make("vorbisdec", "vorbis-decoder");
	conv = gst_element_factory_make("audioconvert", "converter");
	sink = gst_element_factory_make("autoaudiosink", "audio-output");
	//sink = gst_element_factory_make("directsoundsink", "audio-output");
	// Windows에서는 autoaudiosink 대신 directsoundsink 사용

	if (!pipeline || !source || !demuxer || !decoder || !conv || !sink) {
		g_printerr("One element could not be created. Exiting.\n");
		return -1;
	}

	/* Set up the pipeline */

	/* we set the input filename to the source element */
	g_object_set(G_OBJECT(source), "location", argv[1], NULL);

	/* we add a message handler */
	bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
	bus_watch_id = gst_bus_add_watch(bus, bus_call, loop);
	gst_object_unref(bus);

	/* we add all elements into the pipeline */
	/* file-source | ogg-demuxer | vorbis-decoder | converter | alsa-output */
	gst_bin_add_many(GST_BIN(pipeline), source, demuxer, decoder, conv, sink, NULL);

	/* we link the elements together */
	/* file-source -> ogg-demuxer ~> vorbis-decoder -> converter -> alsa-output */
	gst_element_link(source, demuxer);
	gst_element_link_many(decoder, conv, sink, NULL);
	g_signal_connect(demuxer, "pad-added", G_CALLBACK(on_pad_added), decoder);

	/* note that the demuxer will be linked to the decoder dynamically.
	   The reason is that Ogg may contain various streams (for example
	   audio and video). The source pad(s) will be created at run time,
	   by the demuxer when it detects the amount and nature of streams.
	   Therefore we connect a callback function which will be executed
	   when the "pad-added" is emitted.*/

	   /* Set the pipeline to "playing" state*/
	g_print("Now playing: %s\n", argv[1]);
	gst_element_set_state(pipeline, GST_STATE_PLAYING);

	/* Iterate */
	g_print("Running...\n");
	g_main_loop_run(loop);

	/* Out of the main loop, clean up nicely */
	g_print("Returned, stopping playback\n");
	gst_element_set_state(pipeline, GST_STATE_NULL);

	g_print("Deleting pipeline\n");
	gst_object_unref(GST_OBJECT(pipeline));
	g_source_remove(bus_watch_id);
	g_main_loop_unref(loop);

	return 0;
}

 

더 자세한 내용을 알기 위해 on_pad_added()를 위와 같이 수정하고 Debugging - Environment에 추가했던 GST_DEBUG=3을 삭제하고(출력 내용을 간결하게 하기 위해) 실행해 보자.

 

Error code: -4

■ 에러 코드별 의미 및 해결법:
 -1 (GST_PAD_LINK_WRONG_HIERARCHY): 요소(element)들이 서로 같은 파이프라인 버스(Bin) 안에 있지 않을 때. (현재 코드상엔 잘 들어있으므로 제외)
 -2 (GST_PAD_LINK_WAS_LINKED) / -6 (GST_PAD_LINK_REFUSED): 이미 연결되었거나 디코더 측에서 패드 연결을 거부한 경우입니다.
 -4 (GST_PAD_LINK_NOFORMAT): 가장 빈번하게 발생합니다. 디코더와 컨버터, 혹은 컨버터와 오디오 싱크 간에 데이터를 주고받을 포맷 협상(Negotiation)에 실패한 것입니다. 이 경우 다음을 점검해야 합니다.
 ▶오디오 재생 장치 문제: 윈도우 환경에서 autoaudiosink가 기본 오디오 장치를 잘 잡지 못하는 경우가 있습니다. main 함수에서 autoaudiosink를 directsoundsink 또는 wasapisink 로 교체해 보세요.
 ▶Vorbis 플러그인 누락: 개발 환경의 GStreamer 버전에 gst-plugins-base 또는 Ogg 관련 플러그인이 누락되어 디코더가 정상 동작하지 않을 수 있습니다.

 

/* Create gstreamer elements */
pipeline = gst_pipeline_new("audio-player");
source = gst_element_factory_make("filesrc", "file-source");
demuxer = gst_element_factory_make("oggdemux", "ogg-demuxer");
decoder = gst_element_factory_make("vorbisdec", "vorbis-decoder");
conv = gst_element_factory_make("audioconvert", "converter");
//sink = gst_element_factory_make("autoaudiosink", "audio-output");
sink = gst_element_factory_make("directsoundsink", "audio-output");
// Windows에서는 autoaudiosink 대신 directsoundsink 사용

 

main()에서 sink만 위와 같이 수정한다.

 

다시 실행하면 잘 플레이된다.

 

플레이가 끝나면 End of stream 이벤트가 발생하고 종료된다.

 

반응형
Posted by J-sean
:
반응형

유저 모드 애플리케이션이 충돌한 후 덤프를 수집하고 저장하도록 해 보자.

 

위와 같이 레지스트리 값을 작성한다. DumpType 만 작성하고 나머지는 기본값을 사용했다.

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps

Value Description Type Default value
DumpFolder 덤프 파일을 저장할 경로 REG_EXPAND_SZ %LOCALAPPDATA%\CrashDumps
DumpCount 폴더의 최대 덤프 파일 수 REG_DWORD 10
DumpType 다음 덤프 유형 중 하나
0: 사용자 지정 덤프
1: 미니 덤프
2: 전체 덤프
REG_DWORD 1
CustomDumpFlags 사용할 사용자 지정 덤프 옵션. 이 값은 DumpType 이 0으로 설정된 경우에만 사용. REG_DWORD  

 

애플리케이션이 충돌하면 덤프 파일이 생성된다.

 

※ 참고

Collecting User-mode dumps

 

반응형
Posted by J-sean
:
반응형

PC가 아닌 환경에서 작동하는 프로그램을 만든다는건 내게 낮설게만 느껴지는 일이었다. 일반적으로 사용하지 않는, 특별한 용도의 장비(항공, 우주, 의료, 군사 분야 등등..)에나 필요한 전문분야로 생각했기 때문이다. 하지만 스마트폰의 인기와 함께 널리 보급된 안드로이드, iOS는 프로그래머도 아닌 내게 새로운 도전거리(골치거리)를 선물해 주었다. 스마트폰용 앱을 개발해 보는 일이 그것이었다.

 

RTOS중 하나인 LynxOS 로고

 

재미있게도 전부터 '이게 왜 없을까? 정말 없다면 내가 만들면 좋을거 같은데..' 하고 생각 했던 앱이 있긴 했다. 내비게이션을 사용하며 운전하다 보면 목적지에 도착하기 10~15분쯤 전 상대방에게 미리 연락을 하고 싶어진다. 지금 어디쯤 왔으니 한 10분 후에 도착 할거 같다고. 정말 없는건지 나만 못찾는건지는 모르겠지만 어쨌든 보이지 않으니 내가 만들면 되는거 아닌가? (이 앱을 '다왔어'라고 부르기로 하자)

 

기기야 셀 수 없이 많지만 크게 두 가지로 나눌 수 있는 OS의 선택은 굉장히 쉬웠다. 안드로이드. 애플 제품을 좋아하지 않는 나의 유일한 선택지였다. 바로 인터넷에서 안드로이드 관련 내용을 찾아 읽어 보면서 무엇을 어떻게 공부해야 할지 생각했다. 그리고 몇 주간 안드로이드 개발 관련 서적을 공부하며 필요한 내용을 머릿속에 정리하기 시작 했다. 내 위치에서 상대방까지의 거리를 계산하기 위한 GPS기능, 메세지를 보내기 위한 SMS기능, 스마트폰에서 상대방 전화 번호 가져오기 기능 등등.

 

 

공부하기 전에는 알지 못했던 여러가지 권한 주기라든가 안드로이드 버전에 따라 없어지거나 변경된 기능들, 에뮬레이터에서는 되지만 실제 기기에서는 제대로 작동 하지 않는 내용들을 하나 하나 정리해 가며 천천히 준비 했다. 역시 무엇보다도 프로그래밍과는 무관한 회사를 다니며 앱 개발을 준비한다는건 시간적으로나 체력적으로도 쉽지 않은 일이었다. 하지만 시간이 많으면 오히려 정신적으로 나태해지기 쉽다는걸 알기 때문에 내 상황에 불만을 가지지는 않았다.

 

어쨌든 공부해서 대충 완성은 했고.. 간단히 다왔어의 코드를 살펴 보자.

 

1
2
3
4
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission android:name="android.permission.READ_CONTACTS"/>
    <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
    <uses-permission android:name="android.permission.SEND_SMS"/>
 

우선 다왔어 실행에 필요한 권한을 요청하기 위한 준비를 해 준다. 요청할 이름에서도 알 수 있다시피 위치, 연락처, 그리고 SMS 권한이다.

 

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
public void checkPermissions(String[] permissionRequests) {
    final ArrayList<String> permissionRequestList = new ArrayList<String>();
 
    for (final String request : permissionRequests) {
        if (ContextCompat.checkSelfPermission(this, request) != PackageManager.PERMISSION_GRANTED) {
            if (ActivityCompat.shouldShowRequestPermissionRationale(this, request)) {   // Redundant in this case
                permissionRequestList.add(request);
            } else {
                permissionRequestList.add(request);
            }
        }
    }
 
    if (!permissionRequestList.isEmpty()) {
        final String[] results = new String[permissionRequestList.size()];
        permissionRequestList.toArray(results);
 
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("info");
 
        String msg = "This app won't work properly unless you grant below permissions.";
        for (String str : results)
            msg += ("\n- "+ str);
 
        builder.setMessage(msg);
        builder.setIcon(android.R.drawable.ic_dialog_info);
 
        builder.setNeutralButton("OK"new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                ActivityCompat.requestPermissions(MainActivity.this, results, MY_PERMISSION_REQUESTS);
            }
        });
 
        AlertDialog dialog = builder.create();
        dialog.show();
    }
}
 
 

앱 시작시 권한을 요청할 함수이다. 여러가지 권한을 한 번에 요청 할 수 있도록 ArrayList를 사용한다.

 

 

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
findButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        String search = targetText.getText().toString();
        List<Address> addressList;
        try {
            addressList = geocoder.getFromLocationName(search, 10);
            String[] split = addressList.get(0).toString().split(",");
            String address = split[0].substring(split[0].indexOf("\""+ 1split[0].length() - 2);
            String latitude = split[10].substring(split[10].indexOf("="+ 1);  // 위도(수평선)
            String longitude = split[12].substring(split[12].indexOf("="+ 1); // 경도(수직선)
 
            String data = "목적지: " + address;
            targetView.setText(data);
            targetLocation.setLatitude(Double.parseDouble(latitude));
            targetLocation.setLongitude(Double.parseDouble(longitude));
 
            float[] distance = new float[1];
            Location.distanceBetween(myLocation.getLatitude(), myLocation.getLongitude(), targetLocation.getLatitude(), targetLocation.getLongitude(), distance);
            distanceView.setText("현재 위치와의 거리: " + distance[0/ 1000.0 + "km");
 
            isStarted = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
});
 
 

내 위치와 목적지를 파악해 거리를 계산하는 함수이다. 구글 Geocoder를 사용하기 때문에 이 함수 사용시에는 인터넷 연결이 필요하다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void getContacts(String id) {
    Cursor cursor;
 
    try {
        cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                null,
                ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=?",
                new String[] {id},
                null);
 
        if (cursor.moveToFirst()) {
            recipientName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
            recipientNumber = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
            recipientView.setText("받는 사람: " + recipientName + '\n' + "전화번호: " + recipientNumber + '\n');
 
            cursor.close();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}
 
 

스마트폰의 전화번호부를 검색해 이름과 전화번호를 가져오는 함수이다. 공부했던 책의 내용이 에뮬레이터에서는 정상 작동 하지만 실제 기기에서 제대로 작동하지 않아 버그 잡는데 1시간이 넘게 걸렸다.

 

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
public void onLocationChanged(Location location) {
    if (isStarted) {
        double latitude = location.getLatitude();
        double longitude = location.getLongitude();
 
        myLocation.setLatitude(latitude);
        myLocation.setLongitude(longitude);
 
        float[] distance = new float[1];
        Location.distanceBetween(myLocation.getLatitude(), myLocation.getLongitude(), targetLocation.getLatitude(), targetLocation.getLongitude(), distance);
        distanceView.setText("현재 위치와의 거리: " + distance[0/ 1000.0 + "km");
 
        if (distance[0/ 1000.0f < Float.parseFloat(distanceText.getText().toString())){
            try {
                SmsManager smsManager = SmsManager.getDefault();
                smsManager.sendTextMessage(recipientNumber, null, smsText.getText().toString(), nullnull);
                // 문자 전송 알림음
                Uri uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
                Ringtone ringtone = RingtoneManager.getRingtone(getApplicationContext(), uri);
                ringtone.play();
 
                Toast.makeText(getApplicationContext(), "메세지를 보냈습니다. 앱을 종료 합니다.", Toast.LENGTH_SHORT).show();
            } catch (Exception e) {
                Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_SHORT).show();
            }
 
            //  프로그램 종료하기
            isStarted = false;
 
            finish();
        }
    }
}
 
 

내 위치가 바뀔때마다 목적지와의 거리를 다시 계산하고 출력해 주는 함수이다. 지정한 거리 내로 들어오면 메세지를 보내고 앱을 종료한다. 에뮬레이터에서는 이 함수가 3초 정도에 한 번씩 자동 호출 되지만 기기에서는 실제 위치가 바뀔때만 호출되는 차이가 있다는걸 알지 못해 버그 잡는데 시간이 많이 걸렸다.

 

 

단순한 내용의 앱이기 때문에 추가 Fragment나 Activity 없이 단 한 개의 Activity만으로 디자인 했다.

 

 

첫 실행 화면

 

내 스마트폰에 설치하고 직접 테스트하는 영상. 물론 결과는 성공.

 

실제 사용해 보면 알게 되지만 이 앱은 몇 가지 단점이 있다.

 

실제 이동 거리를 계산하는 일반적인 거리 개념이다. 

 

다왔어의 거리 계산 방법. 내 현재 위치와 목적지의 직선 거리를 계산 한다.

 

구글 맵이나 내비게이션 API를 사용하면 실제 이동 거리로 거리 계산을할 수 있을거 같다. 또, 일반적으로 일정 시간이 지나면 스마트폰의 화면이 꺼지게 되는데 다왔어는 화면이 꺼지는 경우 더 이상 작동하지 않는다. 화면을 켜면 다시 거리 계산을 시작하고 작동 한다. 이 점을 방지하기 위해 자동으로 화면이 꺼지지 않도록 했는데 사용자가 전원 버튼을 눌러 화면을 끄거나 다른 앱으로 전환하는 경우 다시 다왔어로 돌아 오지 않는다면 결국 작동 하지 않는다.

 

사실 그 동안 공부한 내용을 빨리 적용해 보고 싶은 마음에 완성도 높은 앱으로 마무리 하지는 못했다. 알고 싶은게 있으면 알 때까지 공부하지만 알게되면 흥미가 좀 떨어져버리는 내 성격 때문인거 같다. 나중에 필요하다면 좀 더 보완해서 완성도를 높이겠지만 그게 언제가 될지는 모르겠다.

 

그 전에 궁금한 사람이 있다면 직접 설치해서 사용해 볼 수 있도록 apk 파일은 아래에 공유한다.

almost.apk
다운로드

 

반응형
Posted by J-sean
: