반응형
블로그 이미지
개발자로서 현장에서 일하면서 새로 접하는 기술들이나 알게된 정보 등을 정리하기 위한 블로그입니다. 운 좋게 미국에서 큰 회사들의 프로젝트에서 컬설턴트로 일하고 있어서 새로운 기술들을 접할 기회가 많이 있습니다. 미국의 IT 프로젝트에서 사용되는 툴들에 대해 많은 분들과 정보를 공유하고 싶습니다.
솔웅

최근에 올라온 글

최근에 달린 댓글

최근에 받은 트랙백

글 보관함

카테고리

오디오, 비디오, 사진 컨트롤 1

2011. 10. 19. 23:18 | Posted by 솔웅


반응형
이제 오디오, 비디오, 사진 컨트롤을 코로나에서는 어떻게 하는지 공부하겠습니다.
첫번째로 오디오에 대해서 알아보겠습니다.

안드로이드 앱을 만들어 보신 분들은 아시겠지만 안드로이드에서는 이미지나 오디오 파일, xml 파일을 인식할 때 확장자는 고려하지 않습니다.
그러니까 aaa.png나 aaa.gif 나 aaa.jpg 이렇게 확장자만 다르고 이름이 같은 파일들은 동일하게 인식을 합니다. (결국엔 에러를 일으키게 됩니다.)
오디오도 마찬가지 인데요. aac, aif, caf, mp3, ogg 등이 오디오에서 사용하는 확장자 인가봅니다. 이름을 정할 때 확장자를 제외한 파일 이름이 유니크 하도록 정해야 합니다.

코로나에서 오디오를 다룰 때 사용하는 채널의 맥시멈은 32 입니다.
그러니까 동시에 32 channel 까지 사용할 수 있습니다.
audio.totalChannels 를 통해서 채널 갯수를 알 수 있습니다.

사운드(sound) 를 load하는 방법은 두가지가 있습니다.
loadSound()와 loadStream() 입니다.
첫번째는 사운드 파일을 모두 메모리에 올려 놓는 것이고 두번째는 play 할 때 스트리밍 하면서 플레이 하는 것입니다.
첫번째는 시간상으로 절약이 되는 대신에 메모리를 많이 차지하겠고 두번째는 시간이 지연될 수는 있지만 메모리를 아낄 수 있겠죠?

위 파일을 받아보세요.
제가 스나이퍼의 총소리를 좋아하는데 그거 비슷한 소리 같아서 자주 이용해요.
그리고 main.lua에 아래 코드를 넣어보세요.
 explosionSound = audio.loadSound("explosion6.wav")
  audio.play(explosionSound)
  audio.play(explosionSound)
  audio.play(explosionSound)
  audio.play(explosionSound)
  audio.play(explosionSound)

audio.play를 한개만 했을 때와 위와 같이 여러개 했을 때와 비교해 보세요.
위와 같이 loadSound로 불러들이면 메모리에 사운드를 로딩해 놓고 있는 상황이기 때문에 동시에 사운드를 들려줍니다.
audio.loadSound 를 audio.loadStream으로 바꾸면 어떻게 될까요?
이 경우엔 audio.play 하면서 파일을 메모리로 일부분씩 불러들이고 (streaming 하고) 소리를 내기때문에 1개의 소리만 들립니다.

loadStream을 하면서 동시에 소리를 내고 싶으면 아래와 같이 해야 됩니다.
explosionSound1 = audio.loadStream("explosion6.wav")
explosionSound2 = audio.loadStream("explosion6.wav")
explosionSound3 = audio.loadStream("explosion6.wav")
explosionSound4 = audio.loadStream("explosion6.wav")
explosionSound5 = audio.loadStream("explosion6.wav")
  audio.play(explosionSound1)
  audio.play(explosionSound2)
  audio.play(explosionSound3)
  audio.play(explosionSound4)
  audio.play(explosionSound5)

이렇게 하니까 동시에 소리가 나네요.

loadSound로 불러들이면 미리 메모리에 올려놓은 상태이기 때문에 즉시 소리를 play할 수 있습니다. 반면에 loadStream을 사용하면 첫번째 chunk를 메모리로 불러들이는데 시간이 좀 걸릴 수 있습니다.

audio.play를 다 하고 메모리를 비우려면 audio.dispose() 함수를 사용해야 합니다.
물론 해당 오디오 파일이 프로그램 끝날 때 까지 계속 사용해야 되면 이 함수를 사용할 일은 없겠지만요. 당연히 프로그램이 종료 될 때 모든 메모리는 릴리즈 됩니다.

audio.dispose()를 확실하게 사용하려면 아래와 같이 하시면 됩니다.
laserSound = audio.loadSound( "laserSound.wav" )
backgroundMusic = audio.loadStream( "backgroundMusic.m4a" )
audio.dispose( laserSound )
audio.dispose( backgroundMusic )
laserSound = nil  -- This makes sure we can't use the handle again
backgroundMusic = nil  -- This makes sure we can't use the handle again

저 사운드를 게임의 스테이지 1에서만 사용하고 그 위 단계에서는 사용하지 않는다면 저렇게 메모리에서 완전히 사라지게 하는것도 좋은 방법이겠죠? 메모리 관리와 퍼포먼스 차원에서요......

audioPlayFrequency를 설정할 수도 있습니다.

config.lua 파일에 아래와 같이 audioPlayFrequency를 설정합니다.
  application =
  {
      content =
      {
          width = 320,
          height = 480,
          scale = "letterbox",
          audioPlayFrequency = 22050
      },
  }
22050Hz이상이 필요하지 않으면 위와 같이 하면 됩니다. 이것보다 높은 Hz가 필요하다면 44100으로 세팅을 하시구요.
코로나에서 제공되는 값은 11025,22050,44100Hz라고 합니다. 그 이외의 것들은 아직 테스트를 해보지 못했다고 하네요.

stereo가 아니라 mono sound를 사용하게 되면 메모리 점유 공간을 반으로 줄일 수 있습니다. 코로나에서는 OpenAL을 사용한다고 하는데요. OpenAL은 mono sound에 대해서 spatialized/3D effects를 제공한다고 합니다. 하지만 스테레오에는 이 3D효과를 제공하지 않는다네요. 오디오 쪽은 제가 잘 몰라서 이 3D 효과가 뭔지는 모르겠습니다. (돌비 서라운드 시스템 지원을 말하나???) 하여간 코로나에서는 아직까지 이 3D 효과는 지원하지 않고 있답니다.

audio.reserveChannels()
오디오를 로딩하고 플레이를 하면 코로나는 자동적으로 특정 채널에 이 오디오를 할당하게 됩니다.
그런데 이 채널에 볼륨이라던지 다른 세팅이 돼 있다면 그리고 1번 오디오를 플레이하고 2,3,4 번이 다른 채널에서 플레이 되다가 5번채널을 플레이 했는데 이것이 1번 오디오를 플레이했던 채널이랑 동일한 채널에 할당 된다면.
미리 그 채널에 맞춰놨던 세팅값이 5번 사운드에도 적용이 될 겁니다.

이런 부분 까지 제어 해야 될 필요성이 있다면 이 함수를 씁니다.
audio.reserveChannels(2) 이런식으로요. 그러면 해당 채널은 자동 할당이 안 되겠죠.

그리고 audio.findFreeChannel()함수도 있는데요.
이것은 해당 채널부터 할당할 채널을 검색하게하는 함수입니다.

local availableChannel = audio.findFreeChannel()
audio.play( laserSound, { channel=availableChannel } )

이렇게 하면 이 가능한 채널보다 높은 숫자의 채널들을 검색해서 할당하게 될 겁니다.

이상 코로나에서의 사운드 컨트롤에 대해서 알아보았습니다.
다음 시간엔 비디오, 카메라, 사진 라이브러리에 대해서 알아보겠습니다.

그리고 이런 미디어를 다룰 때 유용하게 사용할 수 있는 함수들을 알아보겠습니다.
예를 들어 반복 실행, 딜레이, Completion Listener 로 완료 후 어떤 동작 할 수 있도록 하기, 원래 실행 시간보다 길게 실행 되도록 하기, 잠깐 멈추기, 볼륨 조절 등등이요.

그럼 다음에 뵙겠습니다.




반응형

코로나 SDK에서 파일 다루기

2011. 10. 18. 22:51 | Posted by 솔웅


반응형
다른 기능과 마찬가지로 코로나에서는 file control 도 간단한 코딩으로 구현할 수 있습니다.

우선 파일이 저장될 path를 지정해 주셔야 되는데요. path지정은 system.pathForFile 함수를 사용합니다. 다음은 절대경로에 있는(main.lua에 있는) 아이콘 파일을 가리킵니다.
local path = system.pathForFile( "Icon.png", system.ResourceDirectory )
일반적으로 파일을 저장하려면 아래 3가지 종류의 기본 디렉토리 중 하나를 사용하셔야 합니다.
system.DocumentsDirectory : 어플리케이션 세션 사이에서 persist하게 유지될 필요가 있을 때 사용합니다.
system.TemporaryDirectory : 임시 디렉토리 입니다.
system.ResourceDirectory : 어플리케이션의 모든 asset들이 있는 디렉토리. 이곳에 있는 파일을 생성하거나 수정하거나 추가할 수 없습니다.

아래와 같이 코딩 한 다음 실행해 볼께요.
local path = system.pathForFile( "data.txt", system.DocumentsDirectory )
print("path = " .. path)

system.DocumentsDirectory를 print로 찍어봤더니 맥에 있는 코로나 시뮬레이터 디렉토리안이 찍힙니다. 아마 전화기이면 전화기 내의 특정한 장소에 저장이 되겠죠?


system.ResourceDirectory 를 print 로 찍어보면 main.lua 가 있는 그 메인 디렉토리가 나옵니다. 위 소스와 같이 system. 을 찍지 않아도 이렇게 나오네요.


system.TemporaryDirectory 는 system.DocumentsDirectory 와 같은 경로가 찍히는데요. persist하게 유지되느냐 그냥 temporary하게 유지하느냐의 차이가 있습니다.

아래 코드를 보겠습니다.
local path = system.pathForFile( "data.txt", system.DocumentsDirectory )
 
-- io.open opens a file at path. returns nil if no file found
local file = io.open( path, "r" )
if file then
   -- read all contents of file into a string
   local contents = file:read( "*a" )
   print( "Contents of " .. path .. "\n" .. contents )
   io.close( file )
else
   -- create file b/c it doesn't exist yet
   file = io.open( path, "w" )
   local numbers = {1,2,3,4,5,6,7,8,9}
   file:write( "Feed me data!\n", numbers[1], numbers[2], "\n" )
   for _,v in ipairs( numbers ) do file:write( v, " " ) end
   file:write( "\nNo more data\n" )
   io.close( file )
end

1번을 보면 경로를 system.DocumentsDirectory로 하고 파일 이름은 data.txt로 정의했습니다.
4번째줄을 보면 data.txt파일을 io.open 함수를 이용해서 엽니다.
file이 있으면 file:read 로 모두(*a) 읽어옵니다.
이 읽어온 내용을 print로 찍습니다.
그리고 9번째 줄에서 io.close로 파일을 닫습니다.
만약 file이 없으면 10번째 줄 그 다음이 실행 됩니다.
file을 write권한으로 열구요.
file:write 함수를 이용해서 내용을 파일에 넣습니다.
그리고 파일을 닫습니다.

파일을 만들고 내용을 넣고 나중에 그것을 다시 읽고 하는게 무척 간단하죠?

주의할 점은 처음에 언급했듯이 main.lua 파일이 있는 폴더 그러니까 system.ResouceDirectory 로 접근할 때에는 파일을 수정하거나 지우거나 만들어서는 안됩니다.
처음에 앱을 실행할 때 애플리케이션의 정합성을 체크하게 되는데요. 이 때 등록돼 있는 파일과 똑 같은 파일이 그 디렉토리에 있어야 합니다. 만약에 다를 경우 앱이 실행 되지 않습니다.

보안상의 이유로 파일은 그 애플리케이션의 sandbox에 있는 것만 열 수 있습니다. io 라이브러리는 그 path를 필요로 합니다.
그 path는 system.pathForFile을 통해 제공됩니다. (위에 있는 예제에서와 같이요)

io 라이브러리엔 다음과 같은 합수 들이 있습니다.
io.close(file)
io.flush()
io.input(file)
io.lines(filename) : read 모드로 열어서 iterator 로 리턴합니다.
io.open(filename,mode)
  r : read mode (default), w : write mode , a : append mode, r+ : update mode, 이전 데이터들은 남아있는다. w+ : update mode 이전 데이터들은 지워진다. a+ : append update mode, 이전 데이터들은 남아있는다. 파일 마지막부분서부터 추가된다.
io.output
io.popen(prog,mode) : 별도의 프로세스에서 program prog를 시작한다.
io.read = io.input():read
io.tmpfile() : 프로그램이 끝나면 저절로 해당 파일을 없어진다.
io.type(obj)
io.write() = io.output():write

file:close(), file.flush(), file:lines(), file:read(), file:seek(),file:setvbuf(),file:write()

각 함수들에 대한 상세한 내용은 API를 보세요.

Crypto

Corona SDK 는 hash-based 메세지 검증코드를 제공합니다 (HMAC).
이 기능을 제공하는 crypto 라이브러리는 코로나 앱에 pre-install된 external library입니다. 이 라이브러리를 이용하려면 local crypto = require("crypto") 식으로 require한 후 사용이 가능합니다.

crypto.digest( algorithm, string [, raw] )

crypto.hmac( algorithm, string, key [, raw] )

위와같은 함수들이 있습니다.
이 함수들은 제가 설명하기엔 역부족이네요. 뭔지도 잘 모르겠구요. 언제사용하는건지도 모르겠고...

혹시 아시는 분 계시면 조언 부탁드려요.

일단 오늘로 코로나 SDK의 Data and Files 섹션은 모두 끝났습니다.

다음엔 Audio,Video and Photos 에 대해서 공부해 볼까 합니다.

그럼 담 시간에 뵈요.



반응형

코로나에서 SQLITE DB 이용하기

2011. 10. 17. 22:40 | Posted by 솔웅


반응형
오늘은 코로나에서 데이타베이스를 이용하는 방법을 보겠습니다.
안드로이드나 아이폰에서 SQLite라는 DB를 쓰니까 코로나에서도 당연히 SQLite 연동 기능을 제공 하겠죠?

기본적으로 코로나는 아이폰에 맞는 SQLite built-in을 실행합니다. 그리고이것을 안드로이드의 SQLite 버전으로 컴파일 하게 되는데요. 이것은 안드로이드 바이너리 파일(apk 파일)의 사이즈를 약 300k 정도 커지게 만듭니다.

SQLite 은 코로나 시뮬레이터에서도 제공 되므로 시뮬을 통한 테스트도 가능합니다.
(지금 Spin the Bottle 을 코로나 버전으로 옮기고 있는데요. text field, text box 같은 것들은 시뮬에서 제공이 안 되더라구요. 그래서 소스 고친 다음에 빌드하고 디바이스에 인스톨 해서 테스트 해야 하니 많이 번거롭습니다.)

아래 샘플을 보세요.

require "sqlite3"  -- SQLite 3 를 import 한다.
local db = sqlite3.open_memory() -- 메모리에 디비를 만든다.
 
db:exec[[  -- 테이블을 생성한다.
  CREATE TABLE test (id INTEGER PRIMARY KEY, content);
  INSERT INTO test VALUES (NULL, 'Hello World');
  INSERT INTO test VALUES (NULL, 'Hello Lua');
  INSERT INTO test VALUES (NULL, 'Hello Sqlite3')
]]
 
print( "version " .. sqlite3.version() ) -- SQLite의 버전을 터미널에 프린트한다.
 
for row in db:nrows("SELECT * FROM test") do  -- 모든 데이터를 화면에 출력한다.
  local t = display.newText(row.content, 20, 30 * row.id, null, 16)
  t:setTextColor(255,0,255)
end

소스 설명은 옆에 주석으로달았습니다.

두번째 줄에서 보시듯이 이 소스는  디비를 메모리에서 만들도록 했습니다.
그래서 재 실행하면 디비가 없어지고 다시 새로 만들기 때문에 항상 저 위에 3개의 문자만 나옵니다.

아래 샘플 코드는 메모리가 아니라 디바이스에 디비를 만듭니다.

--Include sqlite
require "sqlite3"
--Open data.db.  If the file doesn't exist it will be created
local path = system.pathForFile("data.db", system.DocumentsDirectory)
db = sqlite3.open( path )  
 
--Handle the applicationExit event to close the db
local function onSystemEvent( event )
        if( event.type == "applicationExit" ) then             
            db:close()
        end
end
 
 
--Setup the table if it doesn't exist
local tablesetup = [[CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY, content, content2);]]
print(tablesetup)
db:exec( tablesetup )
 
--Add rows with a auto index in 'id'. You don't need to specify a set of values because we're populating all of them
local testvalue = {}
testvalue[1] = 'Hello'
testvalue[2] = 'World'
testvalue[3] = 'Lua'
local tablefill =[[INSERT INTO test VALUES (NULL, ']]..testvalue[1]..[[',']]..testvalue[2]..[['); ]]
local tablefill2 =[[INSERT INTO test VALUES (NULL, ']]..testvalue[2]..[[',']]..testvalue[1]..[['); ]]
local tablefill3 =[[INSERT INTO test VALUES (NULL, ']]..testvalue[1]..[[',']]..testvalue[3]..[['); ]]
db:exec( tablefill )
db:exec( tablefill2 )
db:exec( tablefill3 )
 
--print the sqlite version to the terminal
print( "version " .. sqlite3.version() )
 
--print all the table contents
for row in db:nrows("SELECT * FROM test") do
  local text = row.content.." "..row.content2
  local t = display.newText(text, 20, 30 * row.id, null, 16)
  t:setTextColor(255,0,255)
end
 
--setup the system listener to catch applicationExit
Runtime:addEventListener( "system", onSystemEvent )

이렇게 디바이스에 디비를 만들면 맨 마지막 줄처럼 앱이 끝날 때 디비를 close 시키기 위해 리스너를 달고 onSystemEvent( event ) 함수처럼 db를 close 시킵니다.


코로나에서는 이 SQLite 관련해서 자세하게 사용법을 알려주지 않더라구요.
그리고 SQLite의 여러 신택스들도 사용하면서 잘 실행이 안되는 것 같구요.

그래서 저는 데이터를 다룰 때 select * 을 해서 모든 데이터를 가져오고 이 데이터들을 코로나의 배열(테이블)에 넣어서 사용했습니다.

function fetchAll()
    local r = {}
    local i = 1;
   
    for row in db:nrows("SELECT * FROM table") do
        --print("id = " .. row.id .. " content = " .. row.content );
        local id = row.id;
        local contnt = row.content;

        r[i] = {}
        r[i].id = id
        r[i].content = contnt
        i = i+1;
    end

return r; -- 모든 데이터와 count(*) 값을 리턴한다.   
end

이렇게 데이터를 r이라는 테이블(배열) 에 담아서 리턴하는 함수 하나 만들어 놓고 사용합니다.
코로나는 간편하게 테이블(배열)을 control 할 수 있어서 이게 더 편하더라구요.

물론 SQLite를 통해서 제공하는 기능을 잘 알면 그걸 쓰면 더 편할 수도 있겠지만요.

신택스는 제가 보니까 아래 처럼 두가지가 있더라구요.
    db:exec[[
        INSERT INTO table VALUES (NULL, 'contents contents contents');
    ]]
이렇게 직접 쿼리를 실행하는 것 하구 아래처럼 변수에 쿼리를 담아서 실행하는 것도 있습니다.

    local insertRule =[[INSERT INTO table VALUES (NULL, ']]..contents..[[');']]
    db:exec( insertRule )

    local updateRule =[[update table SET content = ']] ..content .. [['WHERE id = ']].. id .. [[';']]
    db:exec( updateRule )

자 이렇게 DB control에 필요한 connect, select,insert,update,delete,drop, db close 기능에 대해서 배웠구요.
이 데이터들을 코로나 배열(테이블)에 넣어서 사용하는 법도 배웠습니다.

다음 시간에는 file control에 대해서 살펴 볼께요.

반응형