thumbnailer.c 7.35 KB
Newer Older
Rafaël Carré's avatar
Rafaël Carré committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*****************************************************************************
 * thumbnailer.c
 *****************************************************************************
 * Copyright © 2011-2012 VLC authors and VideoLAN
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

21
22
23
#include <assert.h>
#include <jni.h>
#include <vlc/vlc.h>
Rafaël Carré's avatar
Rafaël Carré committed
24
#include <pthread.h>
25
#include <stdbool.h>
26
27
28
29

#define LOG_TAG "VLC/JNI/thumbnailer"
#include "log.h"

30
31
#include "utils.h"

32
#define THUMBNAIL_POSITION 0.5
Rafaël Carré's avatar
Rafaël Carré committed
33
#define PIXEL_SIZE 4 /* RGBA */
34

Rafaël Carré's avatar
Rafaël Carré committed
35
36
typedef struct
{
37
    libvlc_media_player_t *mp;
Rafaël Carré's avatar
Rafaël Carré committed
38

39
    bool hasThumb;
Rafaël Carré's avatar
Rafaël Carré committed
40

41
42
    char *frameData;
    char *thumbnail;
Rafaël Carré's avatar
Rafaël Carré committed
43

44
45
46
47
    unsigned thumbnailOffset;
    unsigned lineSize;
    unsigned nbLines;
    unsigned picPitch;
Rafaël Carré's avatar
Rafaël Carré committed
48

49
    unsigned nbReceivedFrames;
Rafaël Carré's avatar
Rafaël Carré committed
50
51
52
53
54
55
56
57
58
59
60

    pthread_mutex_t doneMutex;
    pthread_cond_t doneCondVar;
} thumbnailer_sys_t;


/**
 * Thumbnailer vout lock
 **/
static void *thumbnailer_lock(void *opaque, void **pixels)
{
61
62
    thumbnailer_sys_t *sys = opaque;
    *pixels = sys->frameData;
Rafaël Carré's avatar
Rafaël Carré committed
63
64
65
66
67
68
69
    return NULL;
}


/**
 * Thumbnailer vout unlock
 **/
70
static void thumbnailer_unlock(void *opaque, void *picture, void *const *pixels)
Rafaël Carré's avatar
Rafaël Carré committed
71
{
72
    thumbnailer_sys_t *sys = opaque;
Rafaël Carré's avatar
Rafaël Carré committed
73
74

    /* If we have already received a thumbnail, we skip this frame. */
75
76
77
    pthread_mutex_lock(&sys->doneMutex);
    bool hasThumb = sys->hasThumb;
    pthread_mutex_unlock(&sys->doneMutex);
78
    if (hasThumb)
Rafaël Carré's avatar
Rafaël Carré committed
79
80
        return;

81
    sys->nbReceivedFrames++;
Rafaël Carré's avatar
Rafaël Carré committed
82

83
    if (libvlc_media_player_get_position(sys->mp) < THUMBNAIL_POSITION / 2
Rafaël Carré's avatar
Rafaël Carré committed
84
        // Arbitrary choice to work around broken files.
85
86
        && libvlc_media_player_get_length(sys->mp) > 1000
        && sys->nbReceivedFrames < 10)
Rafaël Carré's avatar
Rafaël Carré committed
87
88
89
90
    {
        return;
    }

Rafaël Carré's avatar
Rafaël Carré committed
91
    /* Else we have received our first thumbnail and we can exit. */
92
93
    const char *dataSrc = sys->frameData;
    char *dataDest = sys->thumbnail + sys->thumbnailOffset;
Rafaël Carré's avatar
Rafaël Carré committed
94
    /* Copy the thumbnail. */
Rafaël Carré's avatar
Rafaël Carré committed
95
    unsigned i;
96
    for (i = 0; i < sys->nbLines; ++i)
Rafaël Carré's avatar
Rafaël Carré committed
97
    {
98
        memcpy(dataDest, dataSrc, sys->picPitch);
99
100
        dataDest += sys->lineSize;
        dataSrc += sys->picPitch;
Rafaël Carré's avatar
Rafaël Carré committed
101
102
103
    }

    /* Signal that the thumbnail was created. */
104
105
106
107
    pthread_mutex_lock(&sys->doneMutex);
    sys->hasThumb = true;
    pthread_cond_signal(&sys->doneCondVar);
    pthread_mutex_unlock(&sys->doneMutex);
Rafaël Carré's avatar
Rafaël Carré committed
108
109
}

110
111
112
113
114

/**
 * Thumbnailer main function.
 * return null if the thumbail generation failed.
 **/
Sébastien Toque's avatar
Sébastien Toque committed
115
116
117
jbyteArray Java_org_videolan_vlc_LibVLC_getThumbnail(JNIEnv *env, jobject thiz,
                                                     jint instance, jstring filePath,
                                                     jint width, jint height)
118
{
119
    libvlc_instance_t *libvlc = (libvlc_instance_t *)instance;
120
121
122
    jbyteArray byteArray = NULL;

    /* Create the thumbnailer data structure */
123
124
    thumbnailer_sys_t *sys = calloc(1, sizeof(thumbnailer_sys_t));
    if (sys == NULL)
125
126
    {
        LOGE("Couldn't create the thumbnailer data structure!");
Tanguy Pruvot's avatar
Tanguy Pruvot committed
127
        return NULL;
128
129
130
    }

    /* Initialize the barrier. */
131
132
    pthread_mutex_init(&sys->doneMutex, NULL);
    pthread_cond_init(&sys->doneCondVar, NULL);
133
134

    /* Create a media player playing environment */
135
    sys->mp = libvlc_media_player_new(libvlc);
136

137
    libvlc_media_t *m = new_media(instance, env, thiz, filePath);
138
    if (m == NULL)
139
140
141
142
    {
        LOGE("Couldn't create the media to play!");
        goto end;
    }
143
    libvlc_media_add_option( m, ":no-audio" );
144

145
146
    libvlc_media_player_set_media(sys->mp, m);
    libvlc_media_release(m);
147

148
    /* Get the size of the video with the tracks information of the media. */
149
150
151
152
153
154
155
156
    libvlc_media_track_info_t *tracks;
    libvlc_media_parse(m);
    int nbTracks = libvlc_media_get_tracks_info(m, &tracks);

    unsigned i, videoWidth, videoHeight;
    bool hasVideoTrack = false;
    for (i = 0; i < nbTracks; ++i)
        if (tracks[i].i_type == libvlc_track_video)
157
        {
158
159
160
            videoWidth = tracks[i].u.video.i_width;
            videoHeight = tracks[i].u.video.i_height;
            hasVideoTrack = true;
161
162
163
            break;
        }

164
    free(tracks);
165
166

    /* Abord if we have not found a video track. */
167
    if (!hasVideoTrack)
168
169
170
171
172
173
    {
        LOGE("Could not find a video track in this file.\n");
        goto end;
    }

    /* Compute the size parameters of the frame to generate. */
174
175
176
    unsigned picWidth  = width;
    unsigned picHeight = height;
    float videoAR = (float)videoWidth / videoHeight;
177
178
    float screenAR = (float)width / height;
    if (screenAR < videoAR)
179
    {
180
181
        picHeight = (float)width / videoAR;
        sys->thumbnailOffset = (height - picHeight) / 2 * width * PIXEL_SIZE;
182
183
184
    }
    else
    {
185
186
        picWidth = (float)height * videoAR;
        sys->thumbnailOffset = (width - picWidth) / 2 * PIXEL_SIZE;
187
    }
188

Rafaël Carré's avatar
Rafaël Carré committed
189
    sys->picPitch = picWidth * PIXEL_SIZE;
190
    sys->lineSize = width * PIXEL_SIZE;
191
    sys->nbLines = picHeight;
192

193
    /* Allocate the memory to store the frames. */
194
195
196
    unsigned picSize = sys->picPitch * picHeight;
    sys->frameData = malloc(picSize);
    if (sys->frameData == NULL)
197
198
199
200
201
202
    {
        LOGE("Couldn't allocate the memory to store the frame!");
        goto end;
    }

    /* Allocate the memory to store the thumbnail. */
203
    unsigned thumbnailSize = width * height * PIXEL_SIZE;
204
    sys->thumbnail = calloc(thumbnailSize, 1);
205
    if (sys->thumbnail == NULL)
206
207
208
209
210
211
    {
        LOGE("Couldn't allocate the memory to store the thumbnail!");
        goto end;
    }

    /* Set the video format and the callbacks. */
212
213
214
    libvlc_video_set_format(sys->mp, "RGBA", picWidth, picHeight, sys->picPitch);
    libvlc_video_set_callbacks(sys->mp, thumbnailer_lock, thumbnailer_unlock,
                               NULL, (void*)sys);
215
216

    /* Play the media. */
217
218
    libvlc_media_player_play(sys->mp);
    libvlc_media_player_set_position(sys->mp, THUMBNAIL_POSITION);
219

220
    /* Wait for the thumbnail to be generated. */
221
222
223
224
    pthread_mutex_lock(&sys->doneMutex);
    while (!sys->hasThumb)
        pthread_cond_wait(&sys->doneCondVar, &sys->doneMutex);
    pthread_mutex_unlock(&sys->doneMutex);
225
226

    /* Stop and realease the media player. */
227
228
    libvlc_media_player_stop(sys->mp);
    libvlc_media_player_release(sys->mp);
229
230

    /* Create the Java byte array to return the create thumbnail. */
231
    byteArray = (*env)->NewByteArray(env, thumbnailSize);
232
233
234
235
236
237
    if (byteArray == NULL)
    {
        LOGE("Couldn't allocate the Java byte array to store the frame!");
        goto end;
    }

238
239
    (*env)->SetByteArrayRegion(env, byteArray, 0, thumbnailSize,
                                 (jbyte *)sys->thumbnail);
240

241
    (*env)->DeleteLocalRef(env, byteArray);
242
243

end:
244
245
246
247
248
    pthread_mutex_destroy(&sys->doneMutex);
    pthread_cond_destroy(&sys->doneCondVar);
    free(sys->thumbnail);
    free(sys->frameData);
    free(sys);
249

250
    return byteArray;
251
}