728x90

이전 포스팅까지는 스위치 ON/OFF 예제라던지 등에 Rectangle에 MouseArea를 이용하여 버튼과 비슷한 동작을 만들어 사용했습니다. QtQuick.Controls 모듈을 import할 시 사용할 수 있는 Control Object는 기존의 Button과 같은 UI들을 제공하기 때문에 이 포스팅에서는 몇개의 오브젝트 예제를 다뤄보려 합니다.

 

먼저 Button입니다. 간단하게 Button Object를 생성하면 Item에 MouseArea 처리 없이 Button으로 사용이 가능합니다.

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
    width: 640
    height: 480
    visible: true
    id:windows

    Row {
        anchors.centerIn : parent
        spacing: 10

        Button {
            width: 200
            height : 100
            text : "button"
        }
        Button {
            width: 200
            height : 100
            text : "button"
        }
        Button {
            width: 200
            height : 100
            text : "button"
        }
    }
}

Button의 이벤트 처리도 onClicked같은 signal로 처리 가능합니다.

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
    width: 640
    height: 480
    visible: true
    id:windows

    Row {
        anchors.centerIn : parent
        spacing: 10

        Button {
            width: windows.width / 5
            height : windows.height / 3
            text : "yellow"
            onClicked: {
                rect.color = "yellow"
            }
        }
        Button {
            width: windows.width / 5
            height : windows.height / 3
            text : "blue"
            onClicked: {
                rect.color = "blue"
            }
        }
        Button {
            width: windows.width / 5
            height : windows.height / 3
            text : "red"
            onClicked: {
                rect.color = "red"
            }
        }

        Rectangle {
            id : rect
            width: windows.width / 5
            height : windows.height / 3
            Behavior on color {
                ColorAnimation {
                    duration: 200
                }
            }
        }
    }
}

다음으로 TextInput을 대체할 수 있는 TextField 오브젝트와 TextArea 오브젝트입니다.

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
    width: 500
    height: 300
    visible: true
    id:windows

    Row {
        spacing: 10
        padding: 10
        Column {
            spacing : 10
            TextField {
                id:textfield
                width:windows.width / 2
                height:windows.height / 6
                font.pixelSize: 20
            }
            Rectangle {
                border.width: 1
                width:windows.width / 2
                height:windows.height * 5 / 6
                TextArea {
                    id:textarea
                    anchors.fill: parent
                    font.pixelSize: 20
                    wrapMode: TextEdit.WordWrap
                }
            }
        }

        Button {
            width:windows.width / 6
            height:windows.height / 6
            text: "clear"
            onClicked: {
                textfield.text = ""
                textarea.text = ""
            }
        }
    }
}

TextField와 다르게 TextArea는 여러줄 입력이 가능하고 줄 바꿈에 대한 기준은 TextArae의 wrapMode Property를 통해 설정이 가능합니다.

다음은 CheckBox, ComboBox, Radiobox, SpinBox 오브젝트입니다. 

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
    visible: true
    width : 650
    height: 200
    title: qsTr("CheckBox Demo")

    Item {
        anchors.fill: parent
        Row {
            anchors.centerIn: parent
            spacing: 20

            GroupBox {
                title:"CheckBox"
                anchors.verticalCenter: parent.verticalCenter

                Column {
                    spacing:10
                    CheckBox {
                        text : "Check1"
                    }
                    CheckBox {
                        text : "Check2"
                    }
                    CheckBox {
                        text : "Check3"
                    }
                }
            }

            GroupBox {
                title:"ComboBox"
                anchors.verticalCenter: parent.verticalCenter
                Column {
                    spacing : 10
                    ComboBox {
                        model: [ "Banana", "Apple", "Coconut" ]
                    }
                    ComboBox {
                        model: ListModel {
                            id: cbItems
                            ListElement { text: "Banana"; }
                            ListElement { text: "Apple"; }
                            ListElement { text: "Coconut"; }
                        }
                        onCurrentIndexChanged: console.debug(cbItems.get(currentIndex).text)
                    }
                }
            }

            GroupBox {
                title:"RadioButton"
                anchors.verticalCenter: parent.verticalCenter
                Column {
                    spacing:10
                    RadioButton {
                        text : "Radio1"
                    }
                    RadioButton {
                        text : "Radio2"
                    }
                    RadioButton {
                        text : "Radio3"
                    }
                }
            }

            GroupBox {
                title: "SpinBox"
                anchors.verticalCenter: parent.verticalCenter

                Column {
                                    spacing:10
                    SpinBox {
                        from : 0
                        to : 1000
                        stepSize : 10
                    }

                    SpinBox {
                        id: spinbox
                        from: 0
                        value: 110
                        to: 100 * 100
                        stepSize: 1

                        property int decimals: 2
                        property real realValue: value / 100

                        validator: DoubleValidator {
                            bottom: Math.min(spinbox.from, spinbox.to)
                            top:  Math.max(spinbox.from, spinbox.to)
                        }

                        textFromValue: function(value, locale) {
                            return Number(value / 100).toLocaleString(locale, 'f', spinbox.decimals)
                        }

                        valueFromText: function(text, locale) {
                            return Number.fromLocaleString(locale, text) * 100
                        }
                    }
                }
            }
        }
    }
}

 

728x90
728x90

지난 포스팅에선 간단히 오브젝트와 컨테이너를 이용해서 배치하고 이벤트 처리를 하는 예제를 해봤습니다.

 

[Qt] QML UI 구성하기(1)

지난 포스팅에서 Qt Quick Application 프로젝트를 생성하는 부분까지 포스팅했습니다. [Qt] Qt Quick Application 시작하기 기존에 Qt Widget을 이용하여 UI를 구성하는 방법 말고도 Qml을 이용하여 UI를 구성하

1d1cblog.tistory.com

이번에는 State와 Property 등에 대해 알아보려고 합니다.

 

State는 Transition이라는 개념과 같이 나오는데 스위치를 예를 들자면 스위치는 ON/OFF State가 있고 ON/OFF를 할때마다 설정한 값이 바뀌게 되는데 이에 대한 행동이나 작업을 Trasition이라고 합니다.

 

왼쪽에는 램프가 있고 오른쪽에 스위치를 하나 두었습니다. 스위치를 클릭하면 램프의 상태가 바뀌고 램프는 상태에 따라 Text와 색상이 바뀌께 됩니다.

import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 200
    height: 100
    visible: true

    Row {
        width:200
        height:100
        Rectangle {
            id:lamp
            width:100
            height:100
            border.color: "black"
            Text {
                id: lampText
            }

            state: "off"

            states: [
                State {
                    name: "on"
                    PropertyChanges {
                        target: lamp
                        color:"yellow"
                    }
                    PropertyChanges {
                        target: lampText
                        text:"on"
                        horizontalAlignment: Text.AlignHCenter
                        verticalAlignment: Text.AlignVCenter
                        color:"black"
                    }
                },
                State {
                    name: "off"
                    PropertyChanges {
                        target: lamp
                        color:"black"
                    }
                    PropertyChanges {
                        target: lampText
                        text:"off"
                        horizontalAlignment: Text.AlignHCenter
                        verticalAlignment: Text.AlignVCenter
                        color:"white"
                    }
                }
            ]
        }

        Rectangle {
            id:swit
            width:100
            height:100
            border.color: "black"

            MouseArea {
                anchors.fill: parent
                onClicked: {
                    lamp.state == "on" ? lamp.state = "off" : lamp.state = "on";
                }
            }
        }
    }
}

이 때 on state에서 off state로 이동할 때 처럼 State 간 변화가 있을 때의 행동을 Transitions이라고 합니다.

import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 200
    height: 100
    visible: true

    Row {
        width:200
        height:100
        Rectangle {
            id:lamp
            width:100
            height:100
            border.color: "black"
            Text {
                id: lampText
                width: parent.width
                height: parent.height
                horizontalAlignment: Text.AlignHCenter
                verticalAlignment: Text.AlignVCenter
            }

            state: "off"

            states: [
                State {
                    name: "on"
                    PropertyChanges {
                        target: lamp
                        color:"yellow"
                    }
                    PropertyChanges {
                        target: lampText
                        text:"on"
                        horizontalAlignment: Text.AlignHCenter
                        verticalAlignment: Text.AlignVCenter
                        color:"black"
                    }
                },
                State {
                    name: "off"
                    PropertyChanges {
                        target: lamp
                        color:"black"
                    }
                    PropertyChanges {
                        target: lampText
                        text:"off"
                        horizontalAlignment: Text.AlignHCenter
                        verticalAlignment: Text.AlignVCenter
                        color:"white"
                    }
                }
            ]

            transitions: [
                Transition {
                    from: "off"
                    to: "on"
                    ColorAnimation { duration : 1000 }
                }
            ]
        }

        Rectangle {
            id:swit
            width:100
            height:100
            border.color: "black"
            Text {
                text: "Switch"
                width: parent.width
                height: parent.height
                horizontalAlignment: Text.AlignHCenter
                verticalAlignment: Text.AlignVCenter
            }

            MouseArea {
                anchors.fill: parent
                onClicked: {
                    lamp.state == "on" ? lamp.state = "off" : lamp.state = "on";
                }
            }
        }
    }
}

위 코드에서 off->on으로 상태가 변경될 때는 Color 변경에 대해 Animation을 적용했습니다. 

Qt에서 List나 Table 같은 데이터를 표현하기 위해서 Model 부분과 View 부를 제공합니다. 아래 그림을 보면 Model은 Data를 읽어 담아놓는 Container입니다. View는 Model로부터 읽어온 데이터를 표시하거나 핸들링하게 해줍니다.

먼저 List입니다. List도 마찬가지로 Model과 View로 데이터를 관리/표시 하며 Delegate를 통해 표시할 데이터의 Style을 꾸며줄 수 있습니다.

import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 300
    height: 100
    visible: true

    ListModel {
        id:nameModel
        ListElement { name: "park" }
        ListElement { name: "kim" }
        ListElement { name: "lee" }
    } // 데이터를 담는 Model

    Component {
        id: nameDelegate
        Text {
            text : name
            font.pixelSize: 20
        }
    } // 데이터를 표현하는 Style

    ListView {
        anchors.fill: parent
        model : nameModel
        delegate: nameDelegate
    } // 데이터를 보여주는 View
}

데이터를 변경하고 싶을 땐 Model을 Style을 변경하고 싶을 땐 Component(Delegate)를 실제 표시하는 오브젝트를 변경하고 싶을 때는 View를 수정하시면 됩니다.

 

Delegate는 한 데이터(Element)에 대해 꾸며주는 Style라고 생각하면 좋습니다. 위 예제에서 Element의 데이터가 추가되고 이 추가된 데이터를 표현할 Style을 더 추가해보겠습니다.

import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 300
    height: nameModel.count * 50
    visible: true
    id:windows

    ListModel {
        id:nameModel
        ListElement {
            name: "park"
            age : 20
        }
        ListElement {
            name: "kim"
            age : 25
        }
        ListElement {
            name: "lee"
            age : 30
        }
    } // 데이터를 담는 Model

    Component {
        id: nameDelegate
        Rectangle {
            width : windows.width
            height : windows.height/nameModel.count
            border.color: "black"
            border.width: 1

            Text {
                anchors.fill: parent
                text: "name : " + name + "\n" + "age : " + age;
                verticalAlignment: Text.AlignVCenter
                anchors.leftMargin: 10
            }
        }
    } // 데이터를 표현하는 Style

    ListView {
        anchors.fill: parent
        model : nameModel
        delegate: nameDelegate
    } // 데이터를 보여주는 View
}

여기에 리스트에 데이터를 추가하고 삭제하는 오브젝트도 넣어보겠습니다.

 

먼저 추가하는 코드입니다. 아래 코드에선 UI 크기 관리를 위해서 변수를 사용하였습니다. 변수는 property 키워드를 붙이고 자료형 + 변수명으로 사용 가능합니다.

import QtQuick 2.15
import QtQuick.Window 2.15


Window {
    property int listViewheight : nameModel.count * dataHeight;
    property int dataHeight : 50;
    property int inputUIHeight : 30;
    property int columnSpacing : 10;
    property int nCount : 5;

    width: 300
    height: listViewheight + inputUIHeight + columnSpacing;
    visible: true
    id:windows

    ListModel {
        id:nameModel
        ListElement {
            name: "park"
            age : 20
        }
        ListElement {
            name: "kim"
            age : 25
        }
        ListElement {
            name: "lee"
            age : 30
        }
    } // 데이터를 담는 Model

    Component {
        id: nameDelegate
        Rectangle {
            width : windows.width
            height : dataHeight
            border.color: "black"
            border.width: 1

            Text {
                anchors.fill: parent
                text: "name : " + name + "\n" + "age : " + age;
                verticalAlignment: Text.AlignVCenter
                anchors.leftMargin: 10
            }
        }
    } // 데이터를 표현하는 Style


    Column {
        spacing : columnSpacing
        width : parent.width
        height : parent.height
        ListView {
            width : parent.width
            height : listViewheight
            model : nameModel
            delegate: nameDelegate
        } // 데이터를 보여주는 View

        Row {

            width : parent.width
            height: inputUIHeight

            Rectangle {
                border.width: 1
                width : parent.width/nCount
                height : parent.height
                Text {
                    anchors.fill: parent
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                    text : "name"
                }
            }

            Rectangle {
                width : parent.width/nCount
                height : parent.height
                border.color: "black"
                border.width: 1
                TextInput {
                    id:nameInput
                    anchors.fill: parent
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                    onFocusChanged: {
                        focus? parent.color = "deepskyblue" : parent.color = "white"
                    }
                }
            }

            Rectangle {
                border.width: 1
                width : parent.width/nCount
                height : parent.height
                Text {
                    anchors.fill: parent
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                    text : "age"
                }
            }
            Rectangle {
                width : parent.width/nCount
                height : parent.height
                border.color: "black"
                border.width: 1
                TextInput {
                    id:ageInput
                    anchors.fill: parent
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                    onFocusChanged: {
                        focus? parent.color = "deepskyblue" : parent.color = "white"
                    }
                }
            }
            Rectangle {
                width : parent.width/nCount
                height : parent.height
                color: "gray"
                border.width: 1

                Text {
                    anchors.fill: parent
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                    text : "input"
                }
                MouseArea {
                    anchors.fill: parent
                    onClicked: {
                        nameModel.append({ name: nameInput.text,
                                           age : parseInt(ageInput.text)
                                        })
                        nameInput.text = ""
                        ageInput.text = ""
                        nameInput.focus = false
                        ageInput.focus = false
                    }
                }
            }
        }
    }
}

TextInput이 적용된 Rectangle을 클릭할 시 Focus가 들어가고 그에 따라 Color가 변경됩니다. 데이터를 입력 후 Input 버튼을 누르게 되면 해당 데이터가 리스트에 추가가 됩니다.

다음은 삭제 처리입니다. 삭제를 위해서 클릭한 아이템을 지우는 방식으로 진행했습니다. 어떤 아이템이 클릭되었는지 확인하기 위해서 listView에 highlight component도 추가하였습니다.

import QtQuick 2.15
import QtQuick.Window 2.15


Window {
    property int listViewheight : nameModel.count * dataHeight;
    property int dataHeight : 50;
    property int inputUIHeight : 30;
    property int columnSpacing : 10;
    property int nCount : 6;

    width: 300
    height: listViewheight + inputUIHeight + columnSpacing;
    visible: true
    id:windows

    ListModel {
        id:nameModel
        ListElement {
            name: "park"
            age : 20
        }
        ListElement {
            name: "kim"
            age : 25
        }
        ListElement {
            name: "lee"
            age : 30
        }
    } // 데이터를 담는 Model

    Component {
         id: nameDelegate
         Item {
             width : windows.width
             height : dataHeight

             Text {
                 anchors.fill: parent
                 text: "name : " + name + "\n" + "age : " + age;
                 verticalAlignment: Text.AlignVCenter
                 anchors.leftMargin: 10
             }
             MouseArea {
                 anchors.fill: parent
                 onClicked: nameList.currentIndex = index
             }
         }
     } // 데이터를 표현하는 Style

    Component {
        id:listHighLight
        Rectangle{
            color : "blue"
        }
    } // Highlight

    Column {
        spacing : columnSpacing
        width : parent.width
        height : parent.height
        ListView {
            id : nameList
            width : parent.width
            height : listViewheight
            model : nameModel
            delegate: nameDelegate
            highlight: listHighLight
        } // 데이터를 보여주는 View

        Row {
            width : parent.width
            height: inputUIHeight

            Rectangle {
                border.width: 1
                width : parent.width/nCount
                height : parent.height
                Text {
                    anchors.fill: parent
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                    text : "name"
                }
            }

            Rectangle {
                width : parent.width/nCount
                height : parent.height
                border.color: "black"
                border.width: 1
                TextInput {
                    id:nameInput
                    anchors.fill: parent
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                    onFocusChanged: {
                        focus? parent.color = "deepskyblue" : parent.color = "white"
                    }
                }
            }

            Rectangle {
                border.width: 1
                width : parent.width/nCount
                height : parent.height
                Text {
                    anchors.fill: parent
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                    text : "age"
                }
            }
            Rectangle {
                width : parent.width/nCount
                height : parent.height
                border.color: "black"
                border.width: 1
                TextInput {
                    id:ageInput
                    anchors.fill: parent
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                    onFocusChanged: {
                        focus? parent.color = "deepskyblue" : parent.color = "white"
                    }
                }
            }
            Rectangle {
                width : parent.width/nCount
                height : parent.height
                color: "gray"
                border.width: 1

                Text {
                    anchors.fill: parent
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                    text : "input"
                }
                MouseArea {
                    anchors.fill: parent
                    onClicked: {
                        nameModel.append({ name: nameInput.text,
                                           age : parseInt(ageInput.text)
                                        })
                        nameInput.text = ""
                        ageInput.text = ""
                        nameInput.focus = false
                        ageInput.focus = false
                    }
                }
            }

            Rectangle {
                width : parent.width/nCount
                height : parent.height
                color: "red"
                border.width: 1

                Text {
                    anchors.fill: parent
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                    text : "delete"
                }
                MouseArea {
                    anchors.fill: parent
                    onClicked: {
                        if ( nameModel.count > 0 ) {
                            nameModel.remove(nameList.currentIndex);
                            nameList.currentIndex = -1
                        }
                    }
                }
            }
        }
    }
}

Delegate에 Rectangle 오브젝트를 사용하면 highlight가 제대로 적용되지 않아 Item 오브젝트로 변경하였습니다.

728x90
728x90

지난 포스팅에서 Qt Quick Application 프로젝트를 생성하는 부분까지 포스팅했습니다.

 

[Qt] Qt Quick Application 시작하기

기존에 Qt Widget을 이용하여 UI를 구성하는 방법 말고도 Qml을 이용하여 UI를 구성하는 방법에 대해 공부해보려 합니다. Qt Creator를 실행 후 Create Project > Qt Quick Application을 선택합니다. 프로젝트를

1d1cblog.tistory.com

오늘은 이어서 간단히 UI를 작성해보려 합니다.

 

QML의 구성은 아래와 같습니다. 모듈을 사용하기 위해 import를 가장 상단에 기입합니다. 그 아래로부터 오브젝트들을 기입하는데 오브젝트들에는 그에 맞는 속성(Property)들에 값을 수정해 UI를 구성해줍니다.

import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")
}

Window안에 Rectangle 오브젝트를 배치해 보겠습니다.

import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")

    Rectangle {
        width:360
        height:360
        border.color : "black"
        border.width : 1
        anchors.centerIn: parent
        Text {
            text: qsTr("Quit")
            anchors.centerIn: parent
        }
        MouseArea {
            anchors.fill: parent
            onClicked: {
                Qt.quit();
            }
        }
    }
}

Rectangle 안에 border property를 이용해서 테두리를 설정해줬습니다. 여기서 anchor 속성이 나오는데 오브젝트의 배치를 조정할 수 있는 속성입니다. 이 속성은 두개 이상의 오브젝트를 통해 속성 예제를 보여드리겠습니다.

오브젝트들은 Item 오브젝트로부터 상속받기 때문에 anchors 뿐 아니라 기본 속성은 Item의 Documents에서 확인할 수 있습니다.

다음은 이미지입니다. 이미지는 Image 오브젝트를 통해 사용할 수 있고 윈도우 크기를 이미지에 맞추고 싶다면 아래와 같이 사용하시면 됩니다.

사진에 텍스트도 넣고 싶다면 Text 오브젝트를 이용해 넣을 수 있습니다.

다음은 사용자 이벤트 처리입니다. 첫 예제에서 나온 MouseArea 같은 것들로 이벤트 처리가 가능합니다. MouseArea를 이용하면 오브젝트에 터치 이벤트를 넣을 수 있습니다.

여기서 Text의 wrapMode를 사용하면 Text를 자동 줄바꿈 설정해줄 수 있습니다.

이 Color 변경에 Animiation 속성을 넣어줄 수 있습니다. Behavior 속성을 이용해서 color 속성일 때 Animiation을 지정해 줍니다.

다음은 TextInput 이벤트입니다. Rectangle 안에 TextInput 이벤트를 넣어 키보드 값을 입력받을 수 있고 포커스 유무에 따라 다른 동작을 하게 할 수 있습니다.

다음으로는 Container 종류입니다. 위처럼 연속된 오브젝트나 여러 오브젝트를 배치할 때 효율적으로 사용할 수 있게 해줍니다. Container는 Row, Column, Grid 등이 있습니다.

 

728x90
728x90

Qt5부터 사용할 수 있는 windeployqt는 빌드 시 생성한 실행파일을 단독으로 실행할 수 있게 필요한 라이브러리를 자동으로 추가해주는 툴입니다.

그 후 프로젝트가 저장된 폴더로 이동하면 Release 폴더가 생긴 것을 확인할 수 있습니다.

그 폴더로 들어가보면 exe 파일이 생성되어 있습니다.

이 exe 파일을 바로 실행시키려 하면 다음과 같이 오류창이 뜨게 됩니다.

제대로 exe파일을 실행파일로 만들기 위해서 MinGW 64-bit를 실행시켜 준 다음 아까 exe 파일이 있던 위치로 이동해줍니다.

창에 windeployqt 명령어로 exe파일을 실행합니다.

명령이 완료되면 아까 폴더에 없던 파일들이 생긴 것을 볼 수 있습니다.

이제 다시 exe 파일을 실행시켜보면 제대로 실행이 되는 것을 확인할 수 있습니다.

728x90
728x90

C++11 버전에 람다 함수라는 표현식이 등장합니다. 이는 여러 예제에서 확인할 수 있는데 Qt를 하신다면 QML Quick Application Project를 생성했을 때 main.cpp에서도 확인할 수 있습니다.

QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, 
    [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }
, Qt::QueuedConnection);

람다 함수는 함수 호출 과정을 생략해 시간을 절약하고 적응하면 코드의 가독성을 높일 수 있는 함수 표현 방식입니다. 람다함수는 함수 이름이 없기 때문에 익명 함수라고도 합니다.

 

람다 함수의 문법은 아래와 같습니다.

먼저 대괄호에 둘러싸인 캡처입니다. 람다 함수에서 사용하고자 하는 외부 변수를 여기에 명시합니다. 함수 내에서 그 변수를 사용하기 위해서입니다.

int main() {
    int nHeight = 170;
    [](int nWeight) { cout << "Height = " << nHeight << "Weight = " << nWeight; } (50);

    return 0;
}

위 코드에서 람다 함수 내부에서 main 안에 있는 Height를 사용할 때 오류가 발생합니다.

이렇게 람다 외부에 있는 값을 사용하기 위해서는 캡처를 이용해야 합니다. 캡처에는 [=]와 [&]이 존재합니다. [=]는 외부 변수를 값으로 전달받고 [&]는 참조로 전달받습니다.

[=]를 통해 전달받는 외부 변수는 값을 변경할 수 없습니다. 

#include <iostream>
using namespace std;
int main() {
    int nHeight = 170;
    [=](int nWeight) { 
      cout << "Height = " << nHeight << " Weight = " << nWeight << endl;
      nHeight = 200;
    } (50);

    cout << "Height = " << nHeight << endl;

    return 0;
}

전달받은 값을 변경하기 위해서는 [&] 캡처를 사용해야 합니다.

#include <iostream>
using namespace std;
int main() {
    int nHeight = 170;
    [&](int nWeight) { 
      cout << "Height = " << nHeight << " Weight = " << nWeight << endl;
      nHeight = 200;
    } (50);

    cout << "Height = " << nHeight << endl;

    return 0;
}

캡처를 더 세세하게 설정이 가능합니다. 전달 방식과 변수를 같이 기입하여 세밀하게 설정할 수 있습니다.

  • [&a] : 외부 변수 a만 참조로 전달받아 사용합니다. 다른 외부변수는 사용 불가합니다.
#include <iostream>
using namespace std;
int main() {
    int nHeight = 170;
    int nAge = 20;
    [&nHeight](int nWeight) { 
      cout << "Height = " << nHeight << " Weight = " << nWeight << endl;
      cout << "Age = " << nAge;
      nHeight = 200;
    } (50);

    cout << "Height = " << nHeight << endl;

    return 0;
}

  • [=, &a] : 외부 변수 모두 값으로 전달받아 사용하지만 a 변수만 참조로 전달받아 사용합니다.
#include <iostream>
using namespace std;
int main() {
    int nHeight = 170;
    int nAge = 20;
    [=, &nHeight](int nWeight) { 
      cout << "Height = " << nHeight << " Weight = " << nWeight << endl;
      cout << "Age = " << nAge << endl;
      nHeight = 200;
    } (50);

    cout << "Height = " << nHeight << endl;

    return 0;
}

  • [&, a] : 외부 변수 모두 참조 값으로 전달받아 사용하지만 a 변수만 값으로 전달받아 사용합니다.
#include <iostream>
using namespace std;
int main() {
    int nHeight = 170;
    int nAge = 20;
    [&, nHeight](int nWeight) { 
      cout << "Height = " << nHeight << " Weight = " << nWeight << endl;
      cout << "Age = " << nAge << endl;
      nHeight = 200;
    } (50);

    cout << "Height = " << nHeight << endl;

    return 0;
}

 

다음은 매개변수입니다. 매개변수는 기존 함수 선언과 마찬가지로 생략이 가능하며 사용하지 않을 경우에는 괄호 자체도 넣지 않아도 무관합니다.

#include <iostream>
using namespace std;
int main() {
    int nHeight = 170;
    [&] { 
      cout << "Height = " << nHeight << endl;
      nHeight = 200;
    } ();

    cout << "Height = " << nHeight << endl;

    return 0;
}

다음은 mutable 속성입니다. 캡처를 값에 의한 전달일 때 오류가 나지 않게 해 줍니다. 하지만 결국 값에 의한 전달이기 때문에 람다 내부에서 값을 변경해도 외부에서는 그대로인 것을 확인할 수 있습니다.

#include <iostream>
using namespace std;
int main() {
    int nHeight = 170;
    [=](int nWeight) mutable { 
      cout << "Height = " << nHeight << " Weight = " << nWeight << endl;
      nHeight = 200;
    } (50);

    cout << "Height = " << nHeight << endl;

    return 0;
}

throw는 예외 처리 관련 키워드입니다. 해당 키워드가 없어도 상관없습니다.

#include <iostream>
using namespace std;
int main() {
    int a = 5;
    [=] (int nDiv) throw() {
        try {
            if ( nDiv == 0 ) {
                throw 0;
            }
        cout << a/nDiv << endl;
        }
        catch (int e) {
            cout << "Exception";
        }
    } (0);
    return 0;
}

다음으로 반환형입니다. 기본적으로 람다 함수의 반환형은 void고 return을 사용하기 위해서는 반환형을 지정해야 합니다. 만약 따로 지정 안 하고 return 시에는 return에 맞춰 반환형이 결정되고 반환형을 지정했을 경우에 그에 맞춰 변경됩니다.

#include <iostream>
using namespace std;
int main() {
  int a = 63;
  cout << [=] (int nData)  {
    int nSum = a + nData;
    return nSum;
  } (0) << endl;

  cout << [=] (int nData)->char  {
    int nSum = a + nData;
    return nSum;
  } (0) << endl;
  
  return 0;
}

람다 함수는 아래와 같이도 사용할 수 있습니다. sort 함수 같이 함수를 인자로 넘기는 경우에 함수 자리에 람다 함수를 사용할 수 있습니다. auto 키워드를 이용해서 람다 함수를 변수로 지정하여 사용할 수 있습니다.

#include <iostream>
#include <algorithm>
using namespace std;

void printArr(int pArr[]) {
  for(int i=0;i<10;i++) {
    cout << pArr[i] << " ";
  } cout << endl;
}

int main() {
  int arr[10] = {3,8,19,1,4,22,3,24,51,29};
  printArr(arr);

  sort(arr, arr+10);
  printArr(arr);

  sort(arr, arr+10, [](int a, int b) { return a > b; });
  printArr(arr);

  sort(arr, arr+10);
  printArr(arr);

  auto des = [](int a, int b) { return a > b; };
  sort(arr, arr+10, des);
  printArr(arr);
  
  return 0;
}

 

728x90
728x90

이전 포스팅에서 Qt, C++ 코드 상으로 튜토리얼을 진행하면서 각종 elements들이나 bin들에 대해 살펴봤습니다.

 

[Qt] gstreamer(7) - buffer, GLib

Application와 gstreamer의 pipeline 간에 데이터를 주고받는 내용에 대해 살펴봅니다. 이 예제와 basic tutorial 7이 큰 차이가 없어 이 포스팅에서 한번에 다루겠습니다. Basic tutorial 8: Short-cutting the pipeline Ba

1d1cblog.tistory.com

C++ 상에서 구현하는 것이 아닌 Command를 통해 바로 실행할 수 있는 방법도 있습니다. UDP streaming을 하는 예제를 Command로 진행해 보겠습니다. 

 

videotestsrc를 udp를 통해 streaming 하는 커맨드입니다. udpsink에 host property을 통해 스트리밍 대상을 지정합니다.

gst-launch-1.0 -v videotestsrc ! videoconvert ! jpegenc ! rtpjpegpay ! udpsink host=192.168.254.105 port=5000

elements들을 사용할 때는 파이프라인을 구성할 때 elements 간 sink src 사이의 Capabilities를 봐야 합니다.

다음은 udpsrc를 통해 쏘고 있는 영상을 받아오는 커맨드입니다.

gst-launch-1.0 udpsrc port=5000 ! application/x-rtp,encoding-name=JPEG,payload=26 ! rtpjpegdepay ! jpegdec ! autovideosink

실제 테스트 화면입니다. Source 쪽은 Windows Sink 쪽은 Ubuntu 가상환경에서 진행하였습니다.

다른 코덱에 대해서는 아래 링크 참고하시면 됩니다.

 

GStreamer RTP UDP 카메라 전송 명령

GStreamer 1.0 RTP UDP 카메라 전송 gst-launch-1.0을 이용하여 카메라 영상을 전송하는 방법을 정...

blog.naver.com

 

728x90
728x90

명령 결과를 변수에 저장하고 싶을 때 아래처럼 사용합니다. 예를 들어 특정 프로세스의 PID 값을 활용하고 싶을 때 아래처럼 popen을 통해 값을 저장합니다.

void GetCommandResult()
{
    char buf[512];
    
    FILE* pCmd = popen("pidof -s Process","r");
    fgets(buf, 512, pCmd);
    pid_t pid = strtoul(buf, NULL, 10);
}

 

728x90
728x90

Application와 gstreamer의 pipeline 간에 데이터를 주고받는 내용에 대해 살펴봅니다. 이 예제와 basic tutorial 7이 큰 차이가 없어 이 포스팅에서 한번에 다루겠습니다.

 

Basic tutorial 8: Short-cutting the pipeline

Basic tutorial 8: Short-cutting the pipeline Please port this tutorial to python! Please port this tutorial to javascript! Goal Pipelines constructed with GStreamer do not need to be completely closed. Data can be injected into the pipeline and extracted f

gstreamer.freedesktop.org

Application에서 pipeline으로 데이터를 넣을 수 있는 elements를 appsrc, 그 반대를 appsink라고 합니다. 데이터를 넣는 appsrc는 pull mode / push mode가 있는데 pull의 경우 주기적으로 데이터를 넣어주고 push의 경우 원할 때 넣는 방식으로 진행됩니다. 데이터는 Gstbuffer를 통해 파이프라인을 통과합니다.

QT -= gui

CONFIG += c++17 console
CONFIG -= app_bundle

# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
        main.cpp

DEPENDPATH += \
    $$PWD/include

INCLUDEPATH += \
    $$PWD/include \
    $$PWD/include/gstreamer-1.0 \
    $$PWD/include/glib-2.0/ \
    $$PWD/include/glib-2.0/include \
    $$PWD/include/orc-0.4 \
    $$PWD/lib/glib-2.0/include \

win32: LIBS += -L$$PWD/lib/ -lgstreamer-1.0 -lgobject-2.0 -lglib-2.0 -lintl -lgstaudio-1.0 -lgstbase-1.0

DESTDIR += \
    $$PWD/bin

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

gstaudio-1.0 dll 사용이 필요해 pro 파일도 일부 수정하였습니다.

#include <QCoreApplication>
#include <QDebug>
#include <gst/gst.h>
#include <gst/audio/audio.h>
#include <string.h>

#define CHUNK_SIZE 1024   /* Amount of bytes we are sending in each buffer */
#define SAMPLE_RATE 44100 /* Samples per second we are sending */

/* Structure to contain all our information, so we can pass it to callbacks */
typedef struct _CustomData {
  GstElement *pipeline, *app_source, *tee, *audio_queue, *audio_convert1, *audio_resample, *audio_sink;
  GstElement *video_queue, *audio_convert2, *visual, *video_convert, *video_sink;
  GstElement *app_queue, *app_sink;

  guint64 num_samples;   /* Number of samples generated so far (for timestamp generation) */
  gfloat a, b, c, d;     /* For waveform generation */

  guint sourceid;        /* To control the GSource */

  GMainLoop *main_loop;  /* GLib's Main Loop */
} CustomData;

/* This method is called by the idle GSource in the mainloop, to feed CHUNK_SIZE bytes into appsrc.
 * The idle handler is added to the mainloop when appsrc requests us to start sending data (need-data signal)
 * and is removed when appsrc has enough data (enough-data signal).
 */
static gboolean push_data (CustomData *data) {
  GstBuffer *buffer;
  GstFlowReturn ret;
  int i;
  GstMapInfo map;
  gint16 *raw;
  gint num_samples = CHUNK_SIZE / 2; /* Because each sample is 16 bits */
  gfloat freq;

  /* Create a new empty buffer */
  buffer = gst_buffer_new_and_alloc (CHUNK_SIZE);

  /* Set its timestamp and duration */
  GST_BUFFER_TIMESTAMP (buffer) = gst_util_uint64_scale (data->num_samples, GST_SECOND, SAMPLE_RATE);
  GST_BUFFER_DURATION (buffer) = gst_util_uint64_scale (num_samples, GST_SECOND, SAMPLE_RATE);

  /* Generate some psychodelic waveforms */
  gst_buffer_map (buffer, &map, GST_MAP_WRITE);
  raw = (gint16 *)map.data;
  data->c += data->d;
  data->d -= data->c / 1000;
  freq = 1100 + 1000 * data->d;
  for (i = 0; i < num_samples; i++) {
    data->a += data->b;
    data->b -= data->a / freq;
    raw[i] = (gint16)(500 * data->a);
  }
  gst_buffer_unmap (buffer, &map);
  data->num_samples += num_samples;

  /* Push the buffer into the appsrc */
  g_signal_emit_by_name (data->app_source, "push-buffer", buffer, &ret);

  /* Free the buffer now that we are done with it */
  gst_buffer_unref (buffer);

  if (ret != GST_FLOW_OK) {
    /* We got some error, stop sending data */
    return FALSE;
  }

  return TRUE;
}

/* This signal callback triggers when appsrc needs data. Here, we add an idle handler
 * to the mainloop to start pushing data into the appsrc */
static void start_feed (GstElement *source, guint size, CustomData *data) {
  if (data->sourceid == 0) {
    g_print ("Start feeding\n");
    data->sourceid = g_idle_add ((GSourceFunc) push_data, data);
  }
}

/* This callback triggers when appsrc has enough data and we can stop sending.
 * We remove the idle handler from the mainloop */
static void stop_feed (GstElement *source, CustomData *data) {
  if (data->sourceid != 0) {
    g_print ("Stop feeding\n");
    g_source_remove (data->sourceid);
    data->sourceid = 0;
  }
}

/* The appsink has received a buffer */
static GstFlowReturn new_sample (GstElement *sink, CustomData *data) {
  GstSample *sample;

  /* Retrieve the buffer */
  g_signal_emit_by_name (sink, "pull-sample", &sample);
  if (sample) {
    /* The only thing we do in this example is print a * to indicate a received buffer */
    g_print ("*");
    gst_sample_unref (sample);
    return GST_FLOW_OK;
  }

  return GST_FLOW_ERROR;
}

/* This function is called when an error message is posted on the bus */
static void error_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
  GError *err;
  gchar *debug_info;

  /* Print error details on the screen */
  gst_message_parse_error (msg, &err, &debug_info);
  g_printerr ("Error received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message);
  g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none");
  g_clear_error (&err);
  g_free (debug_info);

  g_main_loop_quit (data->main_loop);
}

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

    CustomData data;
      GstPad *tee_audio_pad, *tee_video_pad, *tee_app_pad;
      GstPad *queue_audio_pad, *queue_video_pad, *queue_app_pad;
      GstAudioInfo info;
      GstCaps *audio_caps;
      GstBus *bus;

      /* Initialize custom data structure */
      memset (&data, 0, sizeof (data));
      data.b = 1; /* For waveform generation */
      data.d = 1;

      /* Initialize GStreamer */
      gst_init (&argc, &argv);

      /* Create the elements */
      data.app_source = gst_element_factory_make ("appsrc", "audio_source");
      data.tee = gst_element_factory_make ("tee", "tee");
      data.audio_queue = gst_element_factory_make ("queue", "audio_queue");
      data.audio_convert1 = gst_element_factory_make ("audioconvert", "audio_convert1");
      data.audio_resample = gst_element_factory_make ("audioresample", "audio_resample");
      data.audio_sink = gst_element_factory_make ("autoaudiosink", "audio_sink");
      data.video_queue = gst_element_factory_make ("queue", "video_queue");
      data.audio_convert2 = gst_element_factory_make ("audioconvert", "audio_convert2");
      data.visual = gst_element_factory_make ("wavescope", "visual");
      data.video_convert = gst_element_factory_make ("videoconvert", "video_convert");
      data.video_sink = gst_element_factory_make ("autovideosink", "video_sink");
      data.app_queue = gst_element_factory_make ("queue", "app_queue");
      data.app_sink = gst_element_factory_make ("appsink", "app_sink");

      /* Create the empty pipeline */
      data.pipeline = gst_pipeline_new ("test-pipeline");

      if (!data.pipeline || !data.app_source || !data.tee || !data.audio_queue || !data.audio_convert1 ||
          !data.audio_resample || !data.audio_sink || !data.video_queue || !data.audio_convert2 || !data.visual ||
          !data.video_convert || !data.video_sink || !data.app_queue || !data.app_sink) {
        g_printerr ("Not all elements could be created.\n");
        return -1;
      }

      /* Configure wavescope */
      g_object_set (data.visual, "shader", 0, "style", 0, NULL);

      /* Configure appsrc */
      gst_audio_info_set_format (&info, GST_AUDIO_FORMAT_S16, SAMPLE_RATE, 1, NULL);
      audio_caps = gst_audio_info_to_caps (&info);
      g_object_set (data.app_source, "caps", audio_caps, "format", GST_FORMAT_TIME, NULL);
      g_signal_connect (data.app_source, "need-data", G_CALLBACK (start_feed), &data);
      g_signal_connect (data.app_source, "enough-data", G_CALLBACK (stop_feed), &data);

      /* Configure appsink */
      g_object_set (data.app_sink, "emit-signals", TRUE, "caps", audio_caps, NULL);
      g_signal_connect (data.app_sink, "new-sample", G_CALLBACK (new_sample), &data);
      gst_caps_unref (audio_caps);

      /* Link all elements that can be automatically linked because they have "Always" pads */
      gst_bin_add_many (GST_BIN (data.pipeline), data.app_source, data.tee, data.audio_queue, data.audio_convert1, data.audio_resample,
          data.audio_sink, data.video_queue, data.audio_convert2, data.visual, data.video_convert, data.video_sink, data.app_queue,
          data.app_sink, NULL);
      if (gst_element_link_many (data.app_source, data.tee, NULL) != TRUE ||
          gst_element_link_many (data.audio_queue, data.audio_convert1, data.audio_resample, data.audio_sink, NULL) != TRUE ||
          gst_element_link_many (data.video_queue, data.audio_convert2, data.visual, data.video_convert, data.video_sink, NULL) != TRUE ||
          gst_element_link_many (data.app_queue, data.app_sink, NULL) != TRUE) {
        g_printerr ("Elements could not be linked.\n");
        gst_object_unref (data.pipeline);
        return -1;
      }

      /* Manually link the Tee, which has "Request" pads */
      tee_audio_pad = gst_element_request_pad_simple (data.tee, "src_%u");
      g_print ("Obtained request pad %s for audio branch.\n", gst_pad_get_name (tee_audio_pad));
      queue_audio_pad = gst_element_get_static_pad (data.audio_queue, "sink");
      tee_video_pad = gst_element_request_pad_simple (data.tee, "src_%u");
      g_print ("Obtained request pad %s for video branch.\n", gst_pad_get_name (tee_video_pad));
      queue_video_pad = gst_element_get_static_pad (data.video_queue, "sink");
      tee_app_pad = gst_element_request_pad_simple (data.tee, "src_%u");
      g_print ("Obtained request pad %s for app branch.\n", gst_pad_get_name (tee_app_pad));
      queue_app_pad = gst_element_get_static_pad (data.app_queue, "sink");
      if (gst_pad_link (tee_audio_pad, queue_audio_pad) != GST_PAD_LINK_OK ||
          gst_pad_link (tee_video_pad, queue_video_pad) != GST_PAD_LINK_OK ||
          gst_pad_link (tee_app_pad, queue_app_pad) != GST_PAD_LINK_OK) {
        g_printerr ("Tee could not be linked\n");
        gst_object_unref (data.pipeline);
        return -1;
      }
      gst_object_unref (queue_audio_pad);
      gst_object_unref (queue_video_pad);
      gst_object_unref (queue_app_pad);

      /* Instruct the bus to emit signals for each received message, and connect to the interesting signals */
      bus = gst_element_get_bus (data.pipeline);
      gst_bus_add_signal_watch (bus);
      g_signal_connect (G_OBJECT (bus), "message::error", (GCallback)error_cb, &data);
      gst_object_unref (bus);

      /* Start playing the pipeline */
      gst_element_set_state (data.pipeline, GST_STATE_PLAYING);

      /* Create a GLib Main Loop and set it to run */
      data.main_loop = g_main_loop_new (NULL, FALSE);
      g_main_loop_run (data.main_loop);

      /* Release the request pads from the Tee, and unref them */
      gst_element_release_request_pad (data.tee, tee_audio_pad);
      gst_element_release_request_pad (data.tee, tee_video_pad);
      gst_element_release_request_pad (data.tee, tee_app_pad);
      gst_object_unref (tee_audio_pad);
      gst_object_unref (tee_video_pad);
      gst_object_unref (tee_app_pad);

      /* Free resources */
      gst_element_set_state (data.pipeline, GST_STATE_NULL);
      gst_object_unref (data.pipeline);


    return a.exec();
}

 

예제의 pipeline은 아래와 같습니다. Tee라는 elements를 통해 하나의 appsrc에서 오는 데이터를 3 갈래로 나눠줍니다.

/* Create the elements */
data.app_source = gst_element_factory_make ("appsrc", "audio_source");
data.tee = gst_element_factory_make ("tee", "tee");
data.audio_queue = gst_element_factory_make ("queue", "audio_queue");
data.audio_convert1 = gst_element_factory_make ("audioconvert", "audio_convert1");
data.audio_resample = gst_element_factory_make ("audioresample", "audio_resample");
data.audio_sink = gst_element_factory_make ("autoaudiosink", "audio_sink");
data.video_queue = gst_element_factory_make ("queue", "video_queue");
data.audio_convert2 = gst_element_factory_make ("audioconvert", "audio_convert2");
data.visual = gst_element_factory_make ("wavescope", "visual");
data.video_convert = gst_element_factory_make ("videoconvert", "video_convert");
data.video_sink = gst_element_factory_make ("autovideosink", "video_sink");
data.app_queue = gst_element_factory_make ("queue", "app_queue");
data.app_sink = gst_element_factory_make ("appsink", "app_sink");

queue를 사용하면 multi thread 환경처럼 사용할 수 있습니다.

appsrc elements에 audio caps를 설정하고 set해줍니다.

/* Configure appsrc */
gst_audio_info_set_format (&info, GST_AUDIO_FORMAT_S16, SAMPLE_RATE, 1, NULL);
audio_caps = gst_audio_info_to_caps (&info);
g_object_set (data.app_source, "caps", audio_caps, "format", GST_FORMAT_TIME, NULL);
g_signal_connect (data.app_source, "need-data", G_CALLBACK (start_feed), &data);
g_signal_connect (data.app_source, "enough-data", G_CALLBACK (stop_feed), &data);

여기서 need-data, enough-data signal이 나오는데 source가 data가 필요할때 need-data signal을 이제 충분히 데이터를 받았을 때 enough-data signal을 발생합니다. 이 기준은 내부 데이터 queue에 있는 데이터 양으로 판단합니다.

appsrc 내부 queue의 데이터가 필요할 때 g_idle_add를 통해 등록한 GLib 함수를 호출하게 됩니다. 이 함수는 mainloop가 idle 상태 즉 우선순위가 급한 일이 없을 때 호출됩니다. 

/* This signal callback triggers when appsrc needs data. Here, we add an idle handler
 * to the mainloop to start pushing data into the appsrc */
static void start_feed (GstElement *source, guint size, CustomData *data) {
    if (data->sourceid == 0) {
        g_print ("Start feeding\n");
        data->sourceid = g_idle_add ((GSourceFunc) push_data, data);
    }
}

push_data 함수에서는 buffer를 생성하여 wave 데이터를 생성하고 push-buffer signal을 발생시켜 buffer를 app_src elements의 source pad로 전달합니다. 이 source pad의 buffer는 결국 Tee->App_queue를 통해 app_sink로 전달될 것입니다.

/* This method is called by the idle GSource in the mainloop, to feed CHUNK_SIZE bytes into appsrc.
 * The idle handler is added to the mainloop when appsrc requests us to start sending data (need-data signal)
 * and is removed when appsrc has enough data (enough-data signal).
 */
static gboolean push_data (CustomData *data) {
    GstBuffer *buffer;
    GstFlowReturn ret;
    int i;
    GstMapInfo map;
    gint16 *raw;
    gint num_samples = CHUNK_SIZE / 2; /* Because each sample is 16 bits */
    gfloat freq;

    /* Create a new empty buffer */
    buffer = gst_buffer_new_and_alloc (CHUNK_SIZE);

    /* Set its timestamp and duration */
    GST_BUFFER_TIMESTAMP (buffer) = gst_util_uint64_scale (data->num_samples, GST_SECOND, SAMPLE_RATE);
    GST_BUFFER_DURATION (buffer) = gst_util_uint64_scale (num_samples, GST_SECOND, SAMPLE_RATE);

    /* Generate some psychodelic waveforms */
    gst_buffer_map (buffer, &map, GST_MAP_WRITE);
    raw = (gint16 *)map.data;
    data->c += data->d;
    data->d -= data->c / 1000;
    freq = 1100 + 1000 * data->d;
    for (i = 0; i < num_samples; i++) {
        data->a += data->b;
        data->b -= data->a / freq;
        raw[i] = (gint16)(500 * data->a);
    }
    gst_buffer_unmap (buffer, &map);
    data->num_samples += num_samples;

    /* Push the buffer into the appsrc */
    g_signal_emit_by_name (data->app_source, "push-buffer", buffer, &ret);

    /* Free the buffer now that we are done with it */
    gst_buffer_unref (buffer);

    if (ret != GST_FLOW_OK) {
        /* We got some error, stop sending data */
        return FALSE;
    }

    return TRUE;
}

app_sink에 new-sample signal을 등록해서 sample이 들어올 때 new_sample 함수가 호출됩니다. 

/* Configure appsink */
g_object_set (data.app_sink, "emit-signals", TRUE, "caps", audio_caps, NULL);
g_signal_connect (data.app_sink, "new-sample", G_CALLBACK (new_sample), &data);
gst_caps_unref (audio_caps);

/* The appsink has received a buffer */
static GstFlowReturn new_sample (GstElement *sink, CustomData *data) {
    GstSample *sample;

    /* Retrieve the buffer */
    g_signal_emit_by_name (sink, "pull-sample", &sample);
    if (sample) {
        /* The only thing we do in this example is print a * to indicate a received buffer */
        g_print ("*");
        gst_sample_unref (sample);
        return GST_FLOW_OK;
    }

    return GST_FLOW_ERROR;
}

내부 queue가 어느정도 찼다고 판단되면 stop_feed 함수가 호출되고 등록한 함수를 제거합니다.

/* This callback triggers when appsrc has enough data and we can stop sending.
 * We remove the idle handler from the mainloop */
static void stop_feed (GstElement *source, CustomData *data) {
    if (data->sourceid != 0) {
        g_print ("Stop feeding\n");
        g_source_remove (data->sourceid);
        data->sourceid = 0;
    }
}

위와 같이 GLib를 사용하기 위해서는 main loop를 run상태로 설정해줘야 합니다.

/* Create a GLib Main Loop and set it to run */
data.main_loop = g_main_loop_new (NULL, FALSE);
g_main_loop_run (data.main_loop);
728x90

+ Recent posts

Buy Me A Coffee